Especificação do protocolo BitTorrent

Neste artigo, disponibilizado em domínio público no site oficial do BitTorrent, é explicado a especificação do protocolo, o que permite aos desenvolvedores criar aplicações que façam uso desse sistema de distribuição de arquivos.

O BotTorrent é um protocolo para distribuição de arquivos. Ele identifica o conteúdo pela URL e é projetado para integrar-se sem problemas a web. A vantagem dele sobre o HTTP é que quando múltiplos downloads do mesmo arquivo acontecem ao mesmo tempo, quem está baixando transmite as partes entre si, tornando possível que a fonte do arquivo suporte uma grande quantidade de conexões para o download com um aumento pequeno na banda.

Um arquivo de distribuição do BitTorrent consiste dessas entidades:

  • Um servidor web comum
  • Um arquivo de meta-informação estático
  • Um tracker do BitTorrent
  • Um ponto de download “original”
  • O navegador web dos usuários finais
  • O aplicativo de download dos usuários finais

Existem idealmente vários usuários finais para um único arquivo.

Para começar a servir arquivos, um hospedeiro precisa seguir os seguintes passos:

  1. Iniciar a execução de um tracker (ou, mais precisamente, já ter um sendo executado).
  2. Iniciar a execução de um servidor web comum, como o apache, ou possuir um em execução
  3. Associar a extensão .torrent com o tipo application/x-bittorrent em seu servidor web.
  4. Gerar um arquivo de meta-informação (.torrent) usando o arquivo completo a ser servido e a URL do tracker.
  5. Colocar o arquivo de meta-informação no servidor.
  6. Associar o arquivo de meta-informação (.torrent) de uma outra página web.
  7. Iniciar um aplicativo de download que já possua o arquivo completo (o ponto de conexão “original”).

Para começar a baixar arquivos, um usuário precisa seguir os seguintes passos:

  1. Instalar o BitTorrent (ou um cliente que suporte o protocolo, como o Vuze, Transmission ou uTorrent).
  2. Navegar na web a procurar de links para arquivos .torrent.
  3. Clicar no link para o arquivo .torrent.
  4. Selecione onde salvar o arquivo localmente, ou selecione um download parcial para dar continuidade.
  5. Espere até o download ser completado.
  6. Feche o aplicativo de download (ele fica transmitindo o arquivo até que isso aconteça).

A conectividade se dá da seguinte forma:

  • Strings são formadas por um prefixo que indica o tamanho dela seguida por dois pontos e a string em si. Por exemplo, 4:spam corresponde a ‘spam’.
  • Inteiros são representados por um ‘i’ seguido pelo número da base 10 seguido por um ‘e’. Por exemplo i3e corresponde a 3 e i-3e corresponde to -3. Inteiros não possuem limitação de tamanho.i-0e não é válido. Todas as codificações com um zero na frente, como i03e, são inválidas, ao contrário de i0e, que obviamente corresponde ao 0.
  • Listas são codificadas com um ‘l’ seguido por seus elementos (também codificadas) seguidas por um ‘e’. Por exemplo, l4:spam4:eggse corresponde a [‘spam’, ‘eggs’].
  • Dicionários são codificados com um ‘d’ seguido por uma lista alternada de chaves e seus valores correspondentes seguidos por um ‘e’.Por exemplo, d3:cow3:moo4:spam4:eggse corresponde a {‘cow’: ‘moo’, ‘spam’: ‘eggs’} andd4:spaml1:a1:bee corresponde a {‘spam’: [‘a’, ‘b’]}. Chaves devem ser strings e estar em ordenadas (como strings brutas, não de forma alfanumérica).

Arquivo de meta-informação são dicionários codificados com as seguintes chaves:

announce
A URL do tracker.
info
Mapeia para um dicionário, com as chaves descritas a seguir. A chave name mapeia uma string codificada com UTF-8que é o nome sugerido para salvar o arquivo (ou diretório). É puramente para consulta. A chave piece length mapeia o número de bytes em que cada parte do arquivo é dividida. Para os propósitos da transferência, os arquivos são dividos em partes pré-fixadas que são todas do mesmo tamanho exceto possivelmente pela última parte que pode ser truncada. piece length é quase sempre uma potência de dois, geralmente 2^18 = 256 K (O BitTorrent desde a versão 3.2 usa 2^20= 1Mpor padrão).pieces mapeia uma string cujo tamanho é um múltiplo de 20. É dividida em strings de tamanho 20, onde cada uma dela é uma hash SHA1 da parte do índice correspondente.Existe também um chave length ou uma files, mas nunca ambas ou nenhuma. Se length estiver presente então o download representa um único arquivo, ao cntrário representa um conjunto de arquivo em uma estrutura de diretório.No caso de um arquivo único, length mapeia o tamanho do arquivo em bytes.Para o propósito das outras chaves, o caso de múltiplos arquivos é tratado como tendo apenas um único arquivo pela concatenação dos arquivo de maneira que eles apareçam na lista de arquivos. A lista de arquivos é o valor que files mapeia, e é uma lista de dicionário contendo as seguintes chaves:length – O tamanho do arquivo em bytes.path – Uma lista de strings codificadas UTF-8 correspondentes aos nomes dos sub-diretórios, sendo que o nome é o próprio nome do arquivo (uma lista de tamanho zero é uma caso de erro).No caso de um arquivo único, a chave nameé o nome do arquivo, e no caso de múltiplos arquivo, é o nome de um diretório.Todas as strings de um arquivo .torrent que contenham texto precisam ser codificadas em UTF-8.

Requisições GET ao tracker possuem as seguintes chaves:

info_hash
A hash sha1 de 20 bytes em forma codificada info valor do arquivo de meta-informações. Observe que essa chave é uma sub-string do arquivo. Esse valor quase que certamente terá de ser liberado.
peer_id
Uma string de tamanho 20 que o aplicativo de download usará como id. Cada aplicativo de download gera seu próprio id de forma aleatória quando um novo download é iniciado. Esse valor irá quase que certamente de ser liberado.
ip
Um parâmetro opcional que fornece o IP (ou nome DNS) que esse par está. Geralmente usado para o ponto de download “original” se ele estiver na mesma máquina do tracker.
port
O número da porta em que o par estará escutando. O comportamento comum é que o aplicativo de download escute na porta 6881, e se esta porta estiver ocupada, ele tentará a 6882, 6883 e assim em diante, desistindo depois da porta 6889.
uploaded
O total de arquivo carregado até o momento, codificado em ASCII base dez.
downloaded
O total de arquivo baixado até o momento, codificado em ASCII base dez.
left
O número de bytes que o par ainda tem de baixar, codificado em ASCII base dez. Observe que esse valor não pode ser calculado a partir do que foi baixado e do tamanho do arquivo já que pode ser uma continuação, e existir uma chance de que alguns dos dados baixados falhem no teste de integridade e tenham de ser re-baixados.
event
Essa é uma chave opcional que mapeia os valores startedcompleted, ou stopped (ou empty, que é o mesmo de não estar presente). Se não estiver presente, este é um dos anúncios feitos em intervalos regulares. Um anúncio usando started é enviado quando o download começa, e um usando completed é enviado quando o download termina. Nenhum aviso completed é enviado se o download se completa quando iniciado. Os aplicativos de download enviam um anúncio com stopped quando eles encerram o download.

As respostas do tracker são codificadas em dicionários. Se uma resposta tiver uma chaves failure reason, então ela mapeia uma string que pode ser lida por humanos que explica porque a consulta falhou, e nenhuma outra chave é necessária. Caso contrário, precisa ter duas chaves: interval, que mapeia o número de segundos que o aplicativo de download deve aguardar entre requisições regulares, e peerspeers mapeia uma lista de dicionários correspondentes aos peers, cada um da qual contém as chaves peer idip, e port, que mapeia para o auto selecionado ID, endereço IP ou nome DNS e número da porta do par, respectivamente. Observe que o aplicativos de download podem fazer requisições não agendadas se um evento ocorrer ou precisarem de mais pares.

O protocolo de pares do BitTorrent opera sobre o TCP. Ele é executado eficientemente sem nenhum ajuste de opções de sockets.

Conexões entre pares são simétricas. Mensagens enviadas em ambas  as direções parecem idênticas, e os dados podem fluir em qualquer direção.

O protocolo de pares faz referência as partes do arquivo pelo índice descrito no arquivo de meta-informação, começando por zero. Quando um par termina de baixar uma parte e verifica se seu hash coincide, ele anuncia que possui essa parte para todos os seus pares.

Conexões contém dois bits de estado em cada ponta: choked ou não, e interestes ou não. Choking é uma notificação que nenhum dado será enviado até que o unchoking aconteça. A idéia por trás desse conceito será explicado mais tarde nesse documento.

Transferência de dados podem existir quando um lado está no status interested e o outro lado não estiver no estado choking. O estado Interest precisa ser mantido atualizado todas as vezes – sempre que o aplicativo de download não tiver algo que eles devem solicitar a um par no estado unchoked, eles precisam expressar a falta de interesse, não importando estarem no estado choked. A implementação dessa propriedade é difícil, mas torna possível para os aplicativo de download saberem quais pares iniciarão o download imediatamente quando estiverem no estado unchoked.

As conexões são iniciadas no estado choked e não interest.

Quando dados estão sendo transferidos, os aplicativos de download podem manter várias requisições de pedaços do arquivo na fila de uma vez de modo a obter uma boa performance do TCP (isso é chamado ‘pipelining’). Por outro lado,  as requisições que não puderem ser escritas no buffer TCP imediatamente devem ser enfileiradas na memória ao invés do buffer de rede da aplicação, de forma que elas possam ser usadas quando o estado choke ocorrer.

O protocolo de pares consiste de uma saudação seguida por um fluxo sem fim de mensagens com tamanho pré-fixado. A saudação começa com o caractere dezenove (decimal) seguido pela string “BitTorrent protocol’. O caractere inicial é o prefixo que indica o tamanho, coloque ele na esperança de que novos protocolos possam fazer o mesmo e ainda assim serem distintos um do outro.

Todos os inteiros enviados em seguida no protocolo são codificados com quatro bytes no formato big-endian.

Em seguida vem o hasj sha1 de 20 bytes da forma codificada info valor do arquivo de meta-informações (Esse é o mesmo valor que é anunciado como info_hash ao tracker, apenas que neste ponto ele não possui aspas). Se os dois lados não enviarem o mesmo valor, eles cortam a conexão. A única exceção possível é se o aplicativo de download quiser fazer múltiplos downloads em uma única porta, eles podem aguardar pela conexão para fornecer um hash para o download primeiro, e responder com o mesmo hash se estiver em sua lista.

Após o hash do download, vem um id do par com 20 bytes que é reportado em requisições do tracker e contido em listas de pares em respostas de trackers. Se o lado que está recebendo o id não coincidir com o que o lado inicial  espera, a conexão é cortada.

E essa é a parte da saudação; em seguida vem um fluxo alternado de prefixos de tamanho e mensagens. As mensagens de tamanho zero são mensagens do tipo keepalive, e são ignoradas. Esse tipo de mensagem é enviada geralmente a cada 2 minutos, mas observe que a conexão pode ser terminada muito mais rapidamente quando dados são esperados.

Todas as mensagens que não sejam do tipo keepalive são iniciadas com um byte único que identifica o seu tipo.

Os valores possíveis são:

  • 0 – choke
  • 1 – unchoke
  • 2 – interested
  • 3 – not interested
  • 4 – have
  • 5 – bitfield
  • 6 – request
  • 7 – piece
  • 8 – cancel

‘choke’, ‘unchoke’, ‘interested’, e ‘not interested’ não possuem nenhuma carga.

‘bitfield’ é enviada apenas como primeira mensagem. Sua carga é um campo de bits onde cada índice que o aplicativo de download envia é marcado com 1 e os demais com 0. Os aplicativos de download que não possuem  nada podem pular a mensagem ‘bitfield’. O primeiro bute do bitfield corresponde aos índices 0-7 do bit mais baixo para o mais alto, respsectivamente. Os próximos serão os bytes 8-15 e assim em diante. Bits dispersos no final são ajustados para o valor 0.

A carga da mensagem  ‘have’ é um número único, que é o índice que indica que o aplicativo de download completou e verificou o hash.

Mensagem ‘request’ contém um índice, início e tamanho. Os últimos dois bytes são deslocamentos de bytes. O tamanho é geralmente uma potência de 2 a menos que seja truncado no final do arqquivo. Todas as implementações atuais  usam o tamanho 2^15.

Mensagem ‘cancel’ tem a mesma carga das mensagens ‘request’. Elas geralmente são enviadas com o final do download, durante o que é chamado ‘mode de fim de jogo’. Quando um download está quase completo, há uma tendência de que as últimas partes de todas  sejam baixadas de uma linha única, o que toma um tempo longo. Para garantir que os poucos pedaços finais sejam baixados rapidamente, em vez de requisitar por todos os pedaços que o aplicativo de download ainda não tenha, é enviado requisições para tudo que todos estejam baixando. Para evitar que isso torne-se horrivelmente ineficiente, é enviada uma mensagem ‘cancel’ para todos  assim que cada pedaço é recebido.

Mensagens ‘piece’ contém um índice, início e a parte que está sendo baixada. Observe que essa mensagem é correlacionada com mensagens ‘request’ implicitamente. É possível que uma parte não esperada chegue se mensagens ‘choke’ e  ‘unchoke’ forem enviadas em rápida sucessão e/ou a transferência estiver muito lenta.

O “asfixiamento” (Choking) da transmissão é feito por várias razões. O controle de congestionamento do TCP se comporta muito mal quando se envia dados de muitas conexões de uma vez. Assim, o “asfixiamento” permite que cada par use um algoritmo de olho por olho para garantir que tenham uma taxa de download consistente.

Existem muitos critérios que um bom algoritmo de “asfixiamento” deve atender. Ele deve exceder u número de uploads simultâneos para uma boa performance do TCP. Deve evitar o asfixiamento e desasfixiamento muito rápido, o que é conhecido como ‘fibrilação’. Deve ser reciproco para os pares que permitam o download. Finalmente, devem tentar usar conexões não usadas de vez em quando para descobrir se elas podem ser melhor aproveitadas que as usadas no momento, o que é conhecido como optimistic unchoking.