Peer-to-peer com a utilizaç˜ao do SCTP para aplicativos de
Transcrição
Peer-to-peer com a utilizaç˜ao do SCTP para aplicativos de
Peer-to-peer com a utilização do SCTP para aplicativos de compartilhamento de arquivos Gustavo Salvadori Baptista do Carmo Dissertação apresentada como requisito parcial para obtenção do grau de Mestre pelo Programa de Pós–graduação em Ciência da Computação da Universidade Federal de Uberlândia Programa de Pós–graduação em Ciência da Computação Faculdade de Computação Universidade Federal de Uberlândia Minas Gerais – Brasil Março de 2006 Gustavo Salvadori Baptista do Carmo PEER-TO-PEER COM A UTILIZAÇÃO DO SCTP PARA APLICATIVOS DE COMPARTILHAMENTO DE ARQUIVOS Dissertação apresentada ao programa de Pós-graduação em Ciência da Computação da Universidade Federal de Uberlândia, como requisito parcial para obtenção do tı́tulo de Mestre em Ciência da Computação. Área de pesquisa: Redes de Computadores. Orientador: Prof. Dr. Jamil Salem Barbar UBERLÂNDIA – Março de 2006 Gustavo Salvadori Baptista do Carmo Peer-to-peer com a utilização do SCTP para aplicativos de compartilhamento de arquivos Dissertação apresentada ao Programa de Pósgraduação em Ciência da Computação da Universidade Federal de Uberlândia, para obtenção do tı́tulo de Mestre. Área de pesquisa: Redes de Computadores. Banca Examinadora: Uberlândia, 24 de Março de 2006. Prof. Dr. Jamil Salem Barbar – UFU (orientador) Prof. Dr. João Cândido Lima Dovicchi – UFSC Prof. Dr. Pedro Frosi Rosa – UFU Dedicado ao Meu irmão. Peer-to-peer com a utilização do SCTP para aplicativos de compartilhamento de arquivos Gustavo Salvadori Baptista do Carmo Resumo As redes peer-to-peer oferecem uma forma eficaz para o compartilhamento de recursos dos computadores. Sob essas redes está a camada de transporte. Na Internet, os protocolos mais utilizados nessa camada são o TCP e o UDP. Outro protocolo da camada de transporte é o SCTP, que oferece funcionalidades adicionais em relação ao TCP e ao UDP que o tornam mais apropriado a determinados tipos de aplicações. Este trabalho tem como objetivo mostrar a viabilidade da utilização do SCTP como protocolo da camada de transporte em aplicativos peer-to-peer de compartilhamento de arquivos, fazendo uso das funcionalidades peculiares a esse protocolo para satisfazer de forma mais eficaz as necessidades destes aplicativos. Palavras-chave: Redes de computadores, Redes Peer-to-peer, SCTP, Camada de transporte. Gustavo Salvadori Baptista do Carmo Abstract Peer-to-peer networks provide an efficient way for sharing computer resources. The transport layer underlies these networks. On the Internet, TCP and UDP are the mostly used protocols in this layer. SCTP is another transport layer protocol: related to TCP and UDP, SCTP offers additional functionalities that makes it more suitable to certain kinds of applications. This work aims to show the viability of the use of SCTP as the transport layer protocol for peer-to-peer file sharing applications, using the peculiar functionalities of this protocol to satisfy the needs of these applications in a more efficient way. Keywords: Computer networks, Peer-to-peer Networks, SCTP, Transport Layer. Copyright c 2006 by GUSTAVO SALVADORI BAPTISTA DO CARMO. Copyright “The copyright of this thesis rests with the author. No quotations from it should be published without the author’s prior written consent and information derived from it should be acknowledged”. vii Agradecimentos Primeiramente a Deus, que me deu esta oportunidade e esteve ao meu lado durante todo o percurso. Ao meu orientador Prof. Dr. Jamil Salem Barbar, pela dedicação e paciência. Ao meu pai José Carlos, à minha mãe Bernadete, ao meu irmão Rodrigo, e à minha namorada Graziela, pelo imensurável apoio. Aos meus amigos do mestrado, pela força, incentivo e, muitas vezes, pelo suporte técnico. Aos amigos do pensionato e da república, pelos momentos de descontração e companhia. Aos professores e funcionários do mestrado. À CAPES - Coordenação de Aperfeiçoamento de Pessoal de Nı́vel Superior, pelo apoio financeiro concedido. viii Sumário Resumo v Abstract vi Agradecimentos viii Lista de Figuras xii Lista de Tabelas xv Lista de Acrônimos xvi 1 Introdução 1 2 O modelo peer-to-peer 6 2.1 Definição . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.2 Aplicabilidade das redes peer-to-peer . . . . . . . . . . . . . . . . . . 7 2.3 Caracterı́sticas das redes peer-to-peer . . . . . . . . . . . . . . . . . . 10 2.4 A classificação das arquiteturas de redes peer-to-peer . . . . . . . . . 12 2.4.1 Arquitetura semi-centralizada intermediada . . . . . . . . . . 13 2.4.2 Arquitetura semi-centralizada não intermediada . . . . . . . . 15 2.4.3 Arquitetura descentralizada não estruturada pura . . . . . . . 16 2.4.4 Arquitetura descentralizada estruturada . . . . . . . . . . . . 20 2.4.5 Arquitetura descentralizada não estruturada baseada em ultranós . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 2.5 Protocolos para sistemas de compartilhamento de arquivos . . . . . . 23 2.5.1 Gnutella . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 ix Sumário x 2.5.2 Napster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 2.5.3 Gnutella2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 2.5.4 eDonkey . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 2.5.5 FastTrack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 2.5.6 Kademlia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 3 Os protocolos da camada de transporte 44 3.1 O protocolo UDP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 3.2 O protocolo TCP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 3.3 O protocolo SCTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 3.4 3.3.1 Histórico do SCTP . . . . . . . . . . . . . . . . . . . . . . . . 51 3.3.2 Caracterı́sticas do SCTP . . . . . . . . . . . . . . . . . . . . . 51 A comparação entre o UDP, o TCP e o SCTP . . . . . . . . . . . . . 58 3.4.1 Limitações do UDP . . . . . . . . . . . . . . . . . . . . . . . . 58 3.4.2 Limitações do protocolo TCP . . . . . . . . . . . . . . . . . . 59 3.4.3 SCTP versus TCP versus UDP . . . . . . . . . . . . . . . . . 60 3.4.4 As similaridades entre o SCTP e o TCP . . . . . . . . . . . . 61 3.4.5 As diferenças entre o SCTP e o TCP . . . . . . . . . . . . . . 62 4 A utilização do protocolo SCTP por aplicativos de compartilhamento de arquivos em peer-to-peer 4.1 4.2 4.3 64 O comportamento de uma rede Gnutella . . . . . . . . . . . . . . . . 67 4.1.1 O tráfego gerado na fase de consulta em uma rede Gnutella . 68 4.1.2 Os tamanhos das mensagens de uma rede Gnutella . . . . . . 72 4.1.3 Discussão a respeito do tempo médio da fase de consulta . . . 73 O problema de HOL em redes peer-to-peer . . . . . . . . . . . . . . . 75 4.2.1 O HOL causado pelo TCP . . . . . . . . . . . . . . . . . . . . 76 4.2.2 A utilização dos multi-fluxos . . . . . . . . . . . . . . . . . . . 78 4.2.3 A utilização da entrega desordenada de mensagens . . . . . . 80 Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer . . . . . . . . . . . . . . . 81 4.3.1 Descrição do ambiente de simulação . . . . . . . . . . . . . . . 81 Sumário 4.3.2 xi A simulação da transferência de mensagens numa rede peerto-peer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 4.3.3 4.4 O desempenho dos protocolos TCP e SCTP . . . . . . . . . . 91 A aplicação peer-to-peer de compartilhamento de arquivos Octopus . 97 4.4.1 O protocolo Gnutella com o uso do SCTP . . . . . . . . . . . 97 4.4.2 O funcionamento do aplicativo Octopus . . . . . . . . . . . . . 98 4.4.3 A arquitetura do aplicativo Octopus 4.4.4 A negociação e a transferência de arquivos no Octopus . . . . 109 5 Conclusão . . . . . . . . . . . . . . 101 114 Referências Bibliográficas 119 Apêndice A 125 Apêndice B 150 Anexo A 187 Lista de Figuras 2.1 Representação das redes peer-to-peer e cliente-servidor . . . . . . . . 2.2 Classificação dos aplicativos peer-to-peer sob a perspectiva do gênero 7 de sistema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.3 Classificação dos aplicativos peer-to-peer sob a perspectiva de mercado 9 2.4 Arquiteturas de redes peer-to-peer . . . . . . . . . . . . . . . . . . . . 13 2.5 Arquitetura semi-centralizada intermediada . . . . . . . . . . . . . . . 14 2.6 Arquitetura semi-centralizada não intermediada . . . . . . . . . . . . 16 2.7 Arquitetura descentralizada não estruturada pura . . . . . . . . . . . 17 2.8 Arquitetura descentralizada estruturada . . . . . . . . . . . . . . . . 21 2.9 Arquitetura descentralizada não estruturada baseada em ultranós . . 22 2.10 Cabeçalho das mensagens Gnutella . . . . . . . . . . . . . . . . . . . 26 2.11 A mensagem PING . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 2.12 A mensagem PONG . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 2.13 A mensagem QUERY . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 2.14 A mensagem QUERYHIT . . . . . . . . . . . . . . . . . . . . . . . . 30 2.15 A estrutura de um resultado da QUERYHIT . . . . . . . . . . . . . . 30 2.16 A mensagem PUSH . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 2.17 Exemplo de Roteamento de PINGs e PONGs . . . . . . . . . . . . . 34 2.18 Exemplo de Roteamento de QUERYs, QUERYHITs e PUSHs . . . . 35 3.1 Comunicação lógica entre dois processos, em dois hosts diferentes . . 45 3.2 Cabeçalho do protocolo UDP . . . . . . . . . . . . . . . . . . . . . . 45 3.3 Cabeçalho do protocolo TCP . . . . . . . . . . . . . . . . . . . . . . 47 3.4 Iniciação de conexão TCP . . . . . . . . . . . . . . . . . . . . . . . . 48 3.5 Estados de uma conexão TCP . . . . . . . . . . . . . . . . . . . . . . 49 xii Lista de Figuras xiii 3.6 Encerramento de conexão TCP . . . . . . . . . . . . . . . . . . . . . 50 3.7 Formato do pacote SCTP . . . . . . . . . . . . . . . . . . . . . . . . 52 3.8 Chunk de dados do SCTP . . . . . . . . . . . . . . . . . . . . . . . . 53 3.9 Iniciação de uma associação SCTP . . . . . . . . . . . . . . . . . . . 55 3.10 Estados de uma associação SCTP . . . . . . . . . . . . . . . . . . . . 56 3.11 Encerramento de conexão SCTP . . . . . . . . . . . . . . . . . . . . . 57 3.12 Caracterı́stica de multi-homing do SCTP . . . . . . . . . . . . . . . . 57 3.13 A conexão TCP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 3.14 A associação SCTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 4.1 As fases de funcionamento de um aplicativo peer-to-peer para compartilhamento de arquivos . . . . . . . . . . . . . . . . . . . . . . . . 65 4.2 A fase de consulta em uma rede Gnutella . . . . . . . . . . . . . . . . 68 4.3 Número de QUERYs por segundo em três diferentes monitores . . . . 69 4.4 Número de QUERYs em um monitor . . . . . . . . . . . . . . . . . . 70 4.5 Número de QUERYHITs por segundo em três diferentes monitores . . 70 4.6 Número de QUERYHITs em um monitor . . . . . . . . . . . . . . . . 71 4.7 Tempo de resposta a uma consulta . . . . . . . . . . . . . . . . . . . 74 4.8 Transmissão de mensagens utilizando o TCP . . . . . . . . . . . . . . 77 4.9 Transmissão de mensagens utilizando os multi-fluxos do SCTP . . . . 79 4.10 Transmissão de mensagens utilizando a entrega desordenada do SCTP 80 4.11 A emulação do comportamento esperado de uma rede de computadores por meio do NIST Net . . . . . . . . . . . . . . . . . . . . . . . . 82 4.12 As pilhas de protocolos utilizadas pelos aplicativos do ambiente de simulação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 4.13 As fases de funcionamento dos aplicativos gerador e receptor de pacotes e echo server . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 4.14 O funcionamento da thread geradora de pacotes . . . . . . . . . . . . 86 4.15 O funcionamento da thread receptora de pacotes . . . . . . . . . . . . 87 4.16 A cálculo da latência de uma mensagem - Exemplo 1 . . . . . . . . . 87 4.17 O cálculo da latência de uma mensagem - Exemplo 2 . . . . . . . . . 88 4.18 O cálculo da latência de uma mensagem - Exemplo 3 . . . . . . . . . 89 4.19 Resultados da primeira etapa com taxa de perda de 0% . . . . . . . . 92 Lista de Figuras xiv 4.20 Resultados da primeira etapa com taxa de perda de 10% . . . . . . . 92 4.21 Resultados da primeira etapa com taxa de perda de 20% . . . . . . . 93 4.22 Média dos resultados da primeira etapa - variação da taxa de perda de 0% a 20% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 4.23 Resultados da segunda etapa com taxa de perda de 0% . . . . . . . . 94 4.24 Resultados da segunda etapa com taxa de perda de 10% . . . . . . . 95 4.25 Resultados da segunda etapa com taxa de perda de 20% . . . . . . . 95 4.26 Média de resultados da segunda etapa - variação da taxa de perda de 0% a 20% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 4.27 Novo cabeçalho para o protocolo Gnutella . . . . . . . . . . . . . . . 97 4.28 O fluxograma de funcionamento do Octopus . . . . . . . . . . . . . . 99 4.29 Um aplicativo peer-to-peer utilizando o TCP na camada de transporte 100 4.30 O aplicativo Octopus utilizando o SCTP na camada de transporte . . 101 4.31 A arquitetura do aplicativo Octopus . . . . . . . . . . . . . . . . . . . 103 4.32 A impressão do cache de associações no shell do Linux . . . . . . . . 104 4.33 A impressão do host cache no shell do Linux . . . . . . . . . . . . . . 104 4.34 A utilização dos multi-fluxos pelo aplicativo Octopus . . . . . . . . . 106 4.35 A interface gráfica do aplicativo Octopus . . . . . . . . . . . . . . . . 108 4.36 O fluxograma do servidor de arquivos do Octopus . . . . . . . . . . . 111 4.37 O fluxograma do cliente de arquivos do Octopus . . . . . . . . . . . . 112 Lista de Tabelas 2.1 Descritores de payload do Gnutella . . . . . . . . . . . . . . . . . . . 26 2.2 Exemplos de protocolos de compartilhamento de arquivos e suas arquiteturas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 3.1 Os chunks do SCTP . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 3.2 Comparação entre o SCTP, o TCP e o UDP . . . . . . . . . . . . . . 60 4.1 Número de QUERYs por segundo - média em uma hora . . . . . . . . 70 4.2 Número de QUERYHITs por segundo - média em uma hora . . . . . 71 4.3 Uma mensagem QUERY na rede Gnutella . . . . . . . . . . . . . . . 72 4.4 Uma mensagem QUERYHIT na rede Gnutella . . . . . . . . . . . . . 72 4.5 Parâmetros dos experimentos da primeira etapa . . . . . . . . . . . . 90 4.6 Parâmetros dos experimentos da segunda etapa . . . . . . . . . . . . 91 xv Lista de Acrônimos ADSL - Asymmetric Digital Subscriber Line AIMD - Additive-increase-multiplicative-decrease AOL - America Online API - Application Programming Interface APS - Adaptive Probabilistic Search ASCII - American Standard Code for Information Interchange BFS - Breadth-first Search CAD - Computer-aided Design CAE - Computer-aided Engineering CAN - Content Addressable Network DFS - Depth-first Search DHT - Distributed Hash Table DNS - Domain Name Server DoS - Denial of Service DRLP - Distributed Resource Location Protocol FTP - File Transfer Protocol GPL - GNU General Public Licence GNU - GNU’s not Unix HOL - Head-of-line Blocking HTL - Hops to Live xvi Lista de Tabelas HTTP - Hypertext Transfer Protocol ICQ - “I seek you” IETF - Internet Engineering Task Force IP - Internet Protocol IRC - Internet Relay Chat ISO - International Organization for Standardization JXTA - Juxtapose MDTP - Multi-Network Datagram Transmission Protocol MP3 - MPEG Audio Layer 3 MSN - The Microsoft Network MSS - Maximum Segment Size MTU - Maximum Transmission Unit NIST - National Institute of Standards and Technology OSI - Open Systems Interconnection PDA - Personal Digital Assistant PPP - Point-to-point Protocol PSNT - Public Switched Telephone Network P2P - Peer-to-peer QoS - Quality of Service RAM - Random-access Memory RFC - Request for Comments RTT - Round-trip Time SACK - Selective Acknoledgement SCTP - Stream Control Transmission Protocol SETI - Search for Extra-terrestrial Intelligence SIGTRAN - Signaling Transport Working Group SMTP - Simple Mail Transfer Protocol xvii Lista de Tabelas TCP - Transmission Control Protocol TSN - Transmission Sequence Number TTL - Time to Live UDP - User Datagram Protocol UTF - Unicode Transformation Format VoIP - Voice over Internet Protocol XML - Extensible Markup Language xviii Capı́tulo 1 Introdução Atualmente, os computadores tornaram-se fundamentais como ferramentas de desenvolvimento, e até mesmo de diversão. Permitem a execução de diferentes tipos de aplicativos, tais como CAD (Computer Aided Design), CAE (Computed Aided Engineering), além daqueles destinados ao entretenimento. Esses computadores são caracterizados por seus recursos disponı́veis, que podem ser fı́sicos ou lógicos, de hardware ou de software, tais como microfones, webcams, impressoras, arquivos, capacidade de processamento e capacidade de armazenamento. Possibilitam a comunicação e o estreitamento das relações humanas por meio de aplicativos que permitem a troca de mensagens e de informações, por exemplo, através de e-mails ou VoIP (Voice over IP ). Para que os computadores troquem informações entre si, é necessário que estejam, de alguma forma, conectados fisicamente através de uma rede de computadores. As redes de computadores podem ser categorizadas por suas topologias, tais como: topologia estrela, ou centralizada, topologia em barra, ou Ethernet, e topologia em anel, ou Token Ring, dentre outras. Uma rede é composta por nós, que podem ser qualquer tipo de dispositivo conectado a ela. Os nós podem ser computadores, PDAs (Personal Digital Assistants), telefones celulares, ou diversos utensı́lios com algum tipo de processamento interno. Em uma rede IP (Internet Protocol ), um nó é qualquer dispositivo que contenha um endereço IP1 . As redes de computadores podem ser implementadas com o uso de uma variedade 1 Endereço IP é uma localização lógica, composto por 4 bytes ou 16 bytes, que designam onde um determinado dispositivo se encontra na rede de computadores. 1 Capı́tulo 1. Introdução 2 de arquiteturas de pilhas de protocolos e topologias de redes de computadores. A arquitetura de pilha de protocolos é uma abstração da divisão dos diferentes tipos de serviços disponı́veis, em um sistema computacional, na forma de camadas. O protocolo é a descrição formal dos formatos de mensagens e de regras que dois nós obedecem para a troca de informações. Cada mensagem é composta basicamente pelos campos de cabeçalho, corpo e rodapé, onde o corpo acomoda a informação, que por sua vez pode ser tratada também como uma mensagem, a ser encaminhada. O protocolo é baseado em um conjunto de serviços que são normalmente oferecidos à camada superior, ou a uma aplicação, utilizando os serviços das camadas inferiores. A pilha de protocolos utilizada na Internet, denominada TCP/IP (Transmission Control Protocol/Internet Protocol ), inclui o IP e outros protocolos de camadas superiores que utilizam seus serviços, tais como TCP e UDP (User Datagram Protocol ) na camada de transporte, e HTTP (Hyper Text Transfer Protocol ) e FTP (File Transfer Protocol ) na camada de aplicação. As redes peer-to-peer, ou redes overlay, são aquelas que atuam na camada de aplicação e são definidas por um protocolo. Sua finalidade é o compartilhamento dos recursos dos computadores, também denominados nós. Exemplos destes recursos são os discos rı́gidos para armazenamento de dados, o poder de processamento, a largura de banda ou até mesmo a presença humana. Essas redes são também tipicamente utilizadas para conectar nós de maneira descentralizada, sem a necessidade de um controle central. A maior diferença, quando se compara o modelo cliente-servidor com o modelo peer-to-peer, é que este último possui apenas um elemento atuante, denominado de servente, enquanto que o modelo cliente-servidor possui dois elementos, denominados cliente e servidor. O servente tem as mesmas funções do cliente e do servidor do modelo cliente-servidor. Clientes são entidades que demandam serviços dos servidores, ao passo que servidores recebem as requisições dos clientes e retornam com os dados ou resultados obtidos. A comunicação entre cliente e servidor é feita por meio de protocolos especı́ficos. Os serventes são entidades que atuam como cliente e servidor ao mesmo tempo para um mesmo propósito [29]. Os nós de uma rede peer-to-peer podem apresentar, também, caracterı́sticas do Capı́tulo 1. Introdução 3 modelo cliente-servidor. Dependendo do grau qualitativo de envolvimento dos nós com este modelo, essas redes podem ser implementadas sob diferentes abordagens, resultando em diferentes arquiteturas, podendo ser classificadas dentro de um espectro que varia de um pólo mais centralizador até um pólo descentralizador. Um dos objetivos deste trabalho é apresentar uma nova classificação para as diversas arquiteturas de redes peer-to-peer existentes [11]. A classificação das arquiteturas é feita a partir do levantamento de suas caracterı́sticas, considerando as funcionalidades intrı́nsecas de cada nó, a organização dos nós, e o algoritmo de roteamento utilizado, definindo, desta maneira, o grau de centralização da arquitetura. Um dos grandes desafios das redes peer-to-peer é garantir o bom desempenho do roteamento de mensagens entre os nós participantes. Uma vez que os recursos estão dinamicamente distribuı́dos pela rede, não se pode ter noções exatas sobre em que local esses recursos se encontram; assim sendo, cada nó conta com um mecanismo de busca para executar a tarefa de obter as informações referentes à localização desses recursos na rede virtual. Esse mecanismo é implementado pelos protocolos da camada de aplicação do modelo de referência OSI (Open Systems Interconnection), que definem os algoritmos de roteamento das mensagens. As redes peer-to-peer atuantes na Internet utilizam, sob a camada de aplicação, a pilha de protocolos TCP/IP. A escolha de qual protocolo deve ser utilizado na camada de transporte é um fator determinante para prover e melhorar as funcionalidades especı́ficas a uma determinada aplicação peer-to-peer. Os protocolos da camada de transporte mais utilizados na Internet são o UDP e o TCP. O UDP é um protocolo orientado à mensagem, não orientado à conexão e não confiável, isto é, não garante que os dados sejam entregues ao destinatário e nem que cheguem na ordem correta. O UDP oferece os serviços de multiplexação e demultiplexação dos datagramas e carrega em seu cabeçalho uma “soma de verificação”, utilizada no controle de integridade dos dados. Já o TCP oferece outros serviços. Este protocolo garante que o segmento será entregue ao destinatário, garante a ordem correta de entrega e ainda implementa os controles de fluxo e de congestionamento. Diferentemente do UDP, o TCP é um protocolo orientado à conexão. Um outro protocolo da camada de transporte é o SCTP (Stream Control Trans- Capı́tulo 1. Introdução 4 mission Protocol ). O SCTP é um protocolo confiável, orientado à conexão, e orientado à mensagem. Pode ser utilizado concorrentemente com o TCP pois implementa, de forma similar, os controles de fluxo e de congestionamento. A camada de transporte pode oferecer à camada de aplicação um canal de relacionamento, criado entre dois sistemas finais, que conecta os processos de maneira virtual, isto é, eles não estão conectados fisicamente, mas do ponto de vista da aplicação, é como se estivessem. No TCP, este relacionamento é chamado de conexão, e possui duas vias, uma em cada sentido, através das quais informações podem ser enviadas e recebidas de maneira full-duplex. No SCTP, o relacionamento criado entre dois sistemas finais é chamado de associação. Esta denominação, diferentemente do TCP, é utilizada pois um número arbitrário de vias, chamadas de fluxos, pode ser criado para ambos os sentidos. O SCTP apresenta funcionalidades adicionais, em relação ao TCP e ao UDP, que o tornam mais adequado para determinados tipos de aplicações, como, por exemplo, para as aplicações peer-to-peer, que podem fazer uso da funcionalidade que provê múltiplos fluxos para a transmissão de dados, e da entrega desordenada de mensagens. Assim, este trabalho tem como objetivo principal mostrar a viabilidade da utilização do protocolo SCTP como protocolo da camada de transporte para aplicativos peer-to-peer de compartilhamento de arquivos [9, 10]. Primeiramente, é feita uma análise sobre o ganho de desempenho na utilização do SCTP, em vez do TCP, na transferência das mensagens entre os nós de uma rede peer-to-peer. Para efetuar esta análise, um ambiente de simulação é desenvolvido. Este ambiente é composto basicamente por dois nós e um gerador de perda e atraso de pacotes. Os nós representam entidades que fazem parte de uma rede peer-to-peer e que trocam mensagens entre si, enquanto que o gerador de perda e atraso de pacotes, implementado por meio do aplicativo NIST Net [3], emula o comportamento que uma rede real provoca nas mensagens trafegadas de um nó a outro, em que parâmetros, tais como perda e atraso de pacotes, podem ser configurados. A partir deste ambiente usa-se o TCP num dado momento, e o SCTP em outro, como protocolos da camada de transporte, em que experimentos práticos são executados baseados na dinâmica de uma rede peer-to-peer real em funcionamento. Com Capı́tulo 1. Introdução 5 o SCTP, as funcionalidades dos multi-fluxos e da entrega desordenada de mensagens são exploradas, a fim de se efetuar a comparação com o protocolo TCP, que não apresenta essas funcionalidades. Este trabalho tem, ainda, como objetivo, mostrar um protótipo de aplicação peer-to-peer que faz uso das funcionalidades do SCTP. Para isso, foi desenvolvido o aplicativo de compartilhamento de arquivos Octopus, que utiliza o SCTP na camada de transporte e o protocolo Gnutella reestruturado na camada de aplicação. São descritas as suas camadas e os seus algoritmos, mostrando a forma que este aplicativo faz uso de algumas funcionalidades do protocolo SCTP em um aplicativo peer-to-peer de compartilhamento de arquivos. Esta dissertação, além deste capı́tulo introdutório, organiza-se como mostrado a seguir. No Capı́tulo 2 são apresentadas informações sobre o modelo peer-to-peer, tais como definições, taxonomias, arquiteturas e protocolos existentes para aplicativos de compartilhamento de arquivos. No Capı́tulo 3, faz-se um levantamento das caracterı́sticas dos protocolos da camada de transporte UDP, TCP e SCTP, e, em seguida, é apresentada uma comparação entre eles. O Capı́tulo 4 apresenta, inicialmente, um estudo sobre o comportamento de uma rede peer-to-peer real em funcionamento. Em seguida, é feita uma discussão sobre a transmissão de mensagens com o uso dos protocolos TCP e SCTP. É descrito, então, o ambiente de simulação utilizado para a análise comparativa entre o TCP e o SCTP. Por fim, apresenta-se os detalhes sobre o desenvolvimento do aplicativo de compartilhamento de arquivos Octopus. O Capı́tulo 5 encerra o trabalho com as considerações finais, incluindo o levantamento da contribuição deste trabalho à comunidade cientı́fica e as propostas para trabalhos futuros. O Apêndice A apresenta os códigos fonte dos aplicativos utilizados no ambiente de simulação e o Apêndice B apresenta o código fonte do aplicativo Octopus. O Anexo A mostra a interface gráfica do aplicativo NIST Net. Capı́tulo 2 O modelo peer-to-peer Este capı́tulo apresenta as definições, as caracterı́sticas e alguns detalhes sobre o modelo peer-to-peer e as redes que o implementam. É apresentada também uma nova classificação das diferentes arquiteturas de redes peer-to-peer. Em seguida, o protocolo Gnutella é mostrado em detalhes, por ser o protocolo de referência para as implementações descritas no Capı́tulo 4. Por fim, são citadas as caracterı́sticas de outros protocolos de redes peer-to-peer mais utilizados na Internet. 2.1 Definição O modelo peer-to-peer define regras para o desenvolvimento de redes descentralizadas, que funcionam sem a necessidade de uma entidade central. O nó, neste modelo, se comparado ao modelo cliente-servidor, deixa de ser somente um cliente ou servidor e passa a ser cliente e servidor simultaneamente; além disso, em vez de apenas consumir recursos ou oferecer recursos, realiza as duas tarefas simultaneamente [46]. As redes peer-to-peer, ou redes overlay, implementam o modelo peer-to-peer. São redes virtuais que têm como finalidade principal o compartilhamento dos recursos dos nós. Estes recursos podem ser os discos rı́gidos para armazenamento de arquivos, o poder de processamento, a largura de banda ou, ainda, a presença humana [36]. Na Internet, as funcionalidades dessas redes residem na camada de aplicação, atuando, assim, sobre as camadas de rede, implementada pelo protocolo IP, e de transporte, implementada, em sua maioria, pelos protocolos UDP ou TCP, da arquitetura de protocolos TCP/IP [20]. 6 2.2. Aplicabilidade das redes peer-to-peer 7 As redes peer-to-peer são compostas por nós, também denominados peers, que podem estar localizados nas bordas ou no centro da rede, possuindo conectividade variável e temporária. Seus endereços, utilizados para definir a localização de um nó na rede virtual, são também variáveis, pois os nós podem conectar-se ou desconectarse da rede conforme necessidade ou falha, sendo assim atribuı́do um novo endereço, quando de sua nova conexão. A Figura 2.1 (a) representa uma topologia de rede que implementa o modelo peer-to-peer, e a Figura 2.1 (b) representa uma topologia de rede que implementa o modelo cliente-servidor. Figura 2.1: Representação das redes peer-to-peer e cliente-servidor Para efetuar algumas de suas tarefas, as redes peer-to-peer podem implementar funcionalidades que apresentam caracterı́sticas do modelo cliente-servidor. Assim sendo, à medida que diferentes caracterı́sticas são incorporadas, diferentes arquiteturas de redes peer-to-peer são abordadas [1]. Essas caracterı́sticas são definidas pelos protocolos de redes peer-to-peer que, além da arquitetura, definem os formatos de mensagens e as regras que dois nós devem obedecer para a troca de informações. 2.2 Aplicabilidade das redes peer-to-peer Os aplicativos peer-to-peer podem ser utilizados para diversas finalidades. Assim sendo, há como classificá-los em diferentes sistemas: sistemas de comunicação, sistemas de compartilhamento de arquivos, sistemas colaborativos e sistemas de tarefas distribuı́das. As plataformas de desenvolvimento dão suporte à implementação destes sistemas, oferecendo frameworks, protocolos e documentações de auxı́lio ao desenvolvimento [29]. Os sistemas de comunicação são os aplicativos peer-to-peer mais populares na Internet. Eles permitem que usuários se comuniquem por texto, áudio ou vı́deo, e 2.2. Aplicabilidade das redes peer-to-peer 8 ainda identifiquem a disponibilidade de outros pares para comunicação. Aplicativos mais elaborados oferecem o serviço de conferência, em que mais de dois usuários podem, simultaneamente, compartilhar uma sessão de comunicação. Exemplos de tais aplicativos têm-se o MSN, o ICQ e o Skype. Os sistemas de compartilhamento de arquivos afetaram com grande impacto a indústria fonográfica, cinematográfica, e todas as outras que trabalham com conteúdos digitalizáveis protegidos por lei. Eles permitem que usuários utilizem e forneçam espaço em disco para o compartilhamento de arquivos de música, áudio, livros digitais ou vı́deos, acessı́veis a todos os nós. Nessa classe de sistemas, os usuários têm acesso irrestrito ao disco rı́gido dos nós compartilhantes, que disponibilizam arquivos para download. Uma busca deve ser feita de modo a encontrar o arquivo requerido. Exemplos de tais aplicativos têm-se o Morpheus, o Shareaza e o Kazaa. Os sistemas colaborativos permitem que usuários troquem informações a respeito de uma determinada tarefa comum, em tempo real, sem a necessidade de um servidor central. Aplicações que implementam esta forma de cooperação organizam as informações de maneira inteligente e permitem que pessoas utilizem seus próprios dados e recuperem dados dos demais. Exemplos de tais aplicativos têm-se o Groove e o Microsoft Netmeeting. Os sistemas de tarefas distribuı́das têm como principal objetivo fazer com que atividades complexas sejam divididas em outras menos complexas e distribuı́das pelos nós que compõem a rede, para que sejam executadas em paralelo. Assim que os resultados de cada tarefa menos complexa estiverem disponı́veis, são retornados à entidade responsável por combiná-los, para resolver o problema da tarefa mais complexa. Exemplos de tais aplicativos têm-se o SETI@Home e o Condor. As plataformas de desenvolvimento oferecem ferramentas para facilitar a construção de sistemas peer-to-peer, e especificam não só protocolos para a troca de mensagens, mas também funções que podem ser utilizadas para a elaboração do código-fonte. A utilização destas plataformas para o desenvolvimento dos sistemas peer-to-peer é opcional. Além dos exemplos citados, há muitos outros softwares disponı́veis na Internet, freeware ou não, que se enquadram em algum gênero desses sistemas. A Figura 2.2 posiciona alguns destes aplicativos em três eixos [29]: o eixo dos sistemas de 2.2. Aplicabilidade das redes peer-to-peer 9 comunicação e colaboração, que possuem funcionalidades similares, o dos sistemas de tarefas distribuı́das e o dos sistemas de compartilhamento de arquivos. No ponto de encontro destes três eixos estão representadas as plataformas de desenvolvimento, que podem ser usadas para a implementação de qualquer gênero de sistema. A Figura 2.2 traz como exemplos de plataformas o .NET e o JXTA. Figura 2.2: Classificação dos aplicativos peer-to-peer sob a perspectiva do gênero de sistema A classificação dos aplicativos peer-to-peer pode ser feita, ainda, sob outra perspectiva [29], além daquela apresentada na Figura 2.2. Esta classificação é mostrada na Figura 2.3. Figura 2.3: Classificação dos aplicativos peer-to-peer sob a perspectiva de mercado Em termos de desenvolvimento, a plataforma JXTA [2] provê infra-estrutura para 2.3. Caracterı́sticas das redes peer-to-peer 10 a criação de vários sistemas peer-to-peer. Logo acima da camada das plataformas, está a camada de aplicações horizontais, que são aquelas que podem ser empregadas em mais de um ramo de mercado, em contraste com as verticais, que são mais especı́ficas. Logo acima da camada de aplicações horizontais são citadas exemplos de aplicativos que podem ser criadas a partir de cada classe de sistema, e logo acima, as indústrias que fariam uso destes aplicativos. Para exemplificar o diagrama, de acordo com a Figura 2.3, pode-se usar a plataforma JXTA para auxiliar a criação de um sistema de comunicação, tal como o de mensagem instantânea, para uso no mercado de comunicação. 2.3 Caracterı́sticas das redes peer-to-peer Os aplicativos peer-to-peer causaram impactos estruturais na Internet. Um destes impactos refere-se ao consumo da largura de banda. Estes aplicativos requerem mais banda de upload do que os aplicativos desenvolvidos sob o modelo cliente-servidor, que vinham sendo os mais utilizados pela grande massa de usuários da Internet. O modelo cliente-servidor é utilizado para implementar redes em que os nós são divididos em dois nı́veis: um nı́vel contendo os clientes, que demandam recursos, e outro, contendo os servidores, que oferecem recursos. Nesse modelo, um mesmo computador pode ser, ao mesmo tempo, cliente e servidor, porém não para um mesmo propósito ou objetivo. Por sua vez, o modelo peer-to-peer implementa redes em que os nós estão em um mesmo nı́vel, que contém os serventes, que atuam simultaneamente como cliente e servidor para um mesmo propósito. Por exemplo, um computador pode ser servidor FTP, e ser cliente HTTP simultaneamente, não implementando o modelo peer-to-peer, pois é cliente e servidor ao mesmo tempo, porém, para propósitos diferentes. No entanto, um computador que atua como cliente, procurando por arquivos e, ao mesmo tempo, oferece os arquivos que possui a outros usuários, atuando assim como servidor, implementa o modelo peer-to-peer, pois é cliente e servidor ao mesmo tempo, para um mesmo propósito. Cada um dos modelos apresenta caracterı́sticas distintas, como descritas a seguir: • Tolerância a falha: Quanto maior a descentralização, maior é a tolerância a 2.3. Caracterı́sticas das redes peer-to-peer 11 falhas. No modelo cliente-servidor, se o servidor falha, o serviço e os recursos oferecidos são gravemente prejudicados. No modelo peer-to-peer, a falha de um nó pouco afeta o bem-estar geral da rede, resultando em um ambiente mais tolerante a falhas, ao implementar a redundância dos recursos; • Segurança: Os usuários das redes peer-to-peer deixam de somente consumir recursos passando, também, a disponibilizar recursos. Isso faz com que os nós estejam mais expostos a falhas de segurança do que no modelo cliente-servidor, em que a exposição é mais restrita ao servidor de recursos. • Redução de custos: Os custos de compra e de manutenção de um servidor de grande porte, que oferece, por exemplo, alto poder de processamento e grande espaço de armazenamento, são muito elevados. O modelo peer-to-peer faz com que esses custos sejam distribuı́dos através dos nós, ao agregar os recursos dos usuários; • Escalabilidade: As redes implementadas segundo o modelo cliente-servidor possuem escalabilidade limitada pois, caso o número de clientes cresça de maneira descontrolada, um determinado servidor pode vir a não suportar a demanda pelos recursos. No modelo peer-to-peer, existe uma melhora em relação à escalabilidade, pois o aumento do número de nós afeta o desempenho do sistema de forma positiva; • Autonomia e privacidade: A presença de servidores pode ser detectada com maior facilidade do que a de usuários de um sistema peer-to-peer. Assim sendo, os usuários podem escolher quais arquivos compartilhar, mesmo que estes arquivos sejam legalmente restritos. Além disso, o usuário escolhe o momento de sua entrada e saı́da da rede, garantindo, assim, a sua autonomia. • Gerenciabilidade: O gerenciamento é o planejamento sistematizado da implementação, monitoramento, e utilização dos serviços oferecidos por intermédio de um sistema computacional. Os sistemas baseados no modelo cliente-servidor são gerenciáveis, enquanto os sistemas baseados no modelo peer-to-peer são auto-organizáveis. Uma rede é dita auto-organizável quando, enquanto o número de conexões cresce ou diminui dentro da malha de nós, a organização 2.4. A classificação das arquiteturas de redes peer-to-peer 12 desses nós se altera de forma independente de uma entidade externa. Essa auto-organização depende de vários fatores tais como interesses comuns dos participantes, estabilidade da rede ou implementação do protocolo da camada de aplicação; • Dinamismo: Os sistemas baseados no modelo cliente-servidor permitem que as buscas por recursos sejam feitas de forma centralizada. Os sistemas baseados no modelo peer-to-peer, devido à sua natureza dinâmica, devem buscar recursos que estão distribuı́dos na rede e que estão sob constantes modificações; • Componentes: Diferentes componentes são utilizados em cada um dos modelos. O DNS (Domain Name Server ), por exemplo, é utilizado como recurso para a descoberta de servidores na rede, substituindo os endereços IP por seqüência de strings mais simples para o usuário. No ambiente peer-to-peer, outros componentes, tais como o web caching e o host caching, são utilizados para o descobrimento de nós; • Protocolos: O HTTP, o SMTP (Simple Mail Transfer Protocol ), e outros protocolos, são utilizados para implementar os serviços oferecidos pelos sistemas baseados no modelo cliente-servidor. Os sistemas baseados no modelo peerto-peer utilizam protocolos especı́ficos, tais como Gnutella, FastTrack, entre outros. As caracterı́sticas de um ou de outro modelo podem fundir-se em um mesmo aplicativo, dependendo de como ele é implementado. Essa conjunção de caracterı́sticas permite que diferentes arquiteturas de redes peer-to-peer sejam desenvolvidas, dependendo do grau qualitativo de envolvimento dos nós em cada um dos modelos peer-to-peer e cliente-servidor [29]. 2.4 A classificação das arquiteturas de redes peer-to-peer As arquiteturas de redes peer-to-peer são divididas em semi-centralizadas e descentralizadas1 . As arquiteturas semi-centralizadas são subclassificadas em semi1 Essa classificação foi elaborada tendo-se a arquitetura centralizada como implementação do modelo cliente-servidor. 2.4. A classificação das arquiteturas de redes peer-to-peer 13 centralizadas intermediadas e semi-centralizadas não intermediadas. As arquiteturas descentralizadas são subclassificadas em descentralizadas estruturadas e descentralizadas não estruturadas. As arquiteturas descentralizadas não estruturadas são divididas em descentralizadas não estruturadas puras e descentralizadas não estruturadas baseadas em ultranós. Ambas podem usufruir de uma abordagem pura ou guiada [15, 21, 38, 46]. A classificação proposta pode ser melhor representada pela Figura 2.4. Figura 2.4: Arquiteturas de redes peer-to-peer A Figura 2.4 posiciona as arquiteturas dentro de um pólo que representa as centralizadas, e outro pólo que representa as descentralizadas. Arquiteturas centralizadas são aquelas que dependem de uma entidade central para seu funcionamento. A arquitetura cliente-servidor, por exemplo, é totalmente centralizada, pois depende do funcionamento do servidor, como entidade central, para que suas tarefas sejam executadas e seus serviços oferecidos aos clientes. As arquiteturas descentralizadas, por sua vez, não dependem de uma entidade central e organizam-se de maneira independente de uma única entidade. 2.4.1 Arquitetura semi-centralizada intermediada A arquitetura semi-centralizada intermediada é a que mais se aproxima, em termos de papéis e responsabilidades dos nós que a compõem, de uma arquitetura 2.4. A classificação das arquiteturas de redes peer-to-peer 14 centralizada. Os nós que desejam enviar mensagens, transferir arquivos, ou trocar qualquer fluxo de dados com outros nós devem, antes de iniciar sua comunicação, autenticar-se em uma entidade central, registrando, assim, sua presença. Toda comunicação vai ser controlada pela entidade central, ou seja, ao enviar dados para o outro nó, estes dados passam por esta entidade central, que os encaminha para o destinatário. A resposta, de maneira análoga, é enviada primeiramente à entidade central, que a encaminha para o nó de origem. Esta arquitetura possui como vantagem a possibilidade de filtrar pacotes, armazenar informações sobre a troca de dados, tais como horário, datas e textos. No entanto, é uma arquitetura pouco escalável, uma vez que o aumento no número de clientes pode facilmente levar à sobrecarga na entidade central. A diferença básica desta arquitetura para uma totalmente centralizada, é que os recursos não estão localizados na entidade central, mas nos nós serventes que o cercam. Esta arquitetura pode, ainda, ser utilizada como solução para o compartilhamento de recursos entre dois nós que estão atrás de firewalls. A arquitetura semi-centralizada intermediada pode ser vista na Figura 2.5. Figura 2.5: Arquitetura semi-centralizada intermediada 2.4. A classificação das arquiteturas de redes peer-to-peer 15 A utilização de uma entidade central mostra que o modelo cliente-servidor é utilizado em conjunção com o modelo peer-to-peer para executar a tarefa de mediar a comunicação entre dois serventes. Isso faz com que esta arquitetura tenda ao pólo centralizador dentro do espectro de classificação, sob a perspectiva do grau de centralização. 2.4.2 Arquitetura semi-centralizada não intermediada Essa arquitetura caracteriza-se pela presença de um servidor central, onde são disponibilizadas informações sobre os recursos que os nós compartilham, mais comumente arquivos, além da localização destes recursos na rede [50]. Todas as consultas, que têm como objetivo buscar recursos, são direcionadas ao servidor central que, ao recebê-las, escolhe os nós mais adequados para satisfazê-las. Em aplicativos de compartilhamento de arquivos, por exemplo, o nó requerente envia uma consulta por um determinado arquivo ao servidor central. Assim que este nó recebe a resposta do servidor, ele se conecta diretamente ao nó que oferece o arquivo, para efetuar a transferência, sem a intervenção do servidor central, caracterizando, então, nesta etapa, o modelo peer-to-peer. É necessário, portanto, que os usuários da rede se autentiquem antes de começar a requisitar ou servir arquivos. Como na arquitetura semi-centralizada intermediada, a escalabilidade desta arquitetura é limitada pois, quando o número de nós aumenta, e mais espaço de armazenamento para os ı́ndices dos recursos são necessários, exigese mais carga de trabalho por parte dos servidores centrais. Contudo, a experiência do Napster mostrou que essa arquitetura é bastante robusta e eficiente [14, 36]. A Figura 2.6 representa a arquitetura em questão. 2.4. A classificação das arquiteturas de redes peer-to-peer 16 Figura 2.6: Arquitetura semi-centralizada não intermediada Uma vantagem peculiar dessa arquitetura é a qualidade dos resultados. As respostas são fiéis, pois o servidor central armazena informações de arquivos em nós atualmente participantes na rede. Além disso, caso o servidor não esteja sobrecarregado, a resposta é imediata e não é necessário o envio de muitas mensagens para atingir a um número razoável de respostas. Se o servidor falha, o sistema todo é interrompido. No entanto, esta arquitetura pode ser organizada de várias formas, a fim de melhorar a tolerância a falhas e o desempenho [50]. 2.4.3 Arquitetura descentralizada não estruturada pura A arquitetura descentralizada não estruturada pura é caracterizada por não possuir um servidor central para autenticar usuários e auxiliar na busca de arquivos, como mostrado na Figura 2.7, assim como não apresenta nenhum tipo de estrutura de organização da rede. Com isso, dois problemas devem ser tratados: como encontrar 2.4. A classificação das arquiteturas de redes peer-to-peer 17 nós para se conectar à rede e como encontrar recursos, por exemplo, arquivos, na malha de nós. Figura 2.7: Arquitetura descentralizada não estruturada pura O primeiro problema é causado pela caracterı́stica dinâmica das redes peer-topeer. Nós se conectam e desconectam de uma maneira não previsı́vel. Com isso, diferentes técnicas devem ser utilizadas para encontrar os nós disponı́veis na rede, tais como web caching e pong caching [17, 18, 44]. Para a solução do segundo problema, pode-se utilizar algoritmos sob a estratégia cega, ou sob a estratégia guiada [48]. A estratégia cega propaga uma consulta para um número suficiente de nós, sem a utilização de informação de consultas prévias. Já a guiada utiliza informações sobre consultas passadas para escolher a melhor rota para as mensagens. O exemplo tradicional de uma estratégia cega é o algoritmo utilizado pela rede Gnutella [17,18]. Esse algoritmo executa inundação baseado em BFS (Breadth First 2.4. A classificação das arquiteturas de redes peer-to-peer 18 Search), ou seja, cada consulta é enviada a todos os nós diretamente conectados ao nó requerente. O envio de mensagens é feito por cada nó que recebe a consulta, até que ocorra um número máximo de encaminhamentos, definido pelo parâmetro TTL (Time To Live), decrementado a cada iteração. Esse algoritmo apresenta um alto grau de consumo de largura de banda. A partir da boa manipulação do número de conexões de cada nó e da configuração apropriada do valor do parâmetro TTL das mensagens, esta arquitetura pode ser utilizada por centenas de milhares de nós, requerendo alta capacidade dos enlaces de comunicação para proporcionar desempenho razoável, apresentando problemas de escalabilidade, quando o objetivo é alcançar todos os nós em uma rede [35]. O DFS (Depth First Search) processa a consulta localmente e, aleatoriamente, escolhe um de seus nós vizinhos para encaminhar a mensagem de busca. Este algoritmo também pode ser referenciado como chain mode, em contraste com o broadcast mode do BFS. Há, também, a possibilidade de se implementar esse algoritmo sob a abordagem guiada [5]. O BFS modificado [49] é uma variação do algoritmo de inundação, em que os nós escolhem aleatoriamente uma parcela de seus vizinhos para encaminhar as mensagens de requisições. Esse algoritmo reduz a produção média de mensagens em comparação com o BFS tradicional, mas, mesmo assim, causa um alto consumo de largura de banda. O algoritmo de aprofundamento iterativo [21, 51] usa buscas BFS consecutivas com profundidades cada vez maiores. Alcança melhores resultados quando a condição de término de propagação de consulta está relacionada com o número de resultados definido pelo usuário, sendo possı́vel que poucas iterações satisfaça a consulta. Em casos diferentes deste, o desempenho do algoritmo pode ser ainda pior do que o BFS tradicional. No algoritmo de caminhos aleatórios [51], o nó requerente envia k mensagens para um número k de nós vizinhos escolhidos aleatoriamente. Cada uma destas mensagens segue seu próprio caminho; os nós intermediários encaminham-as para nós aleatórios a cada iteração. Essas consultas podem também ser chamadas de “andarilhos”. Cada “andarilho” pode encerrar seu trajeto a partir de uma falha, ou de um sucesso. A falha pode ser dada por esgotamento de TTL ou pelo método 2.4. A classificação das arquiteturas de redes peer-to-peer 19 de checagem, quando o “andarilho” confere com o nó raiz se a sua consulta já foi satisfeita, ou um número definido de respostas já foi atingido. A grande vantagem desse algoritmo é a redução significativa no número de mensagens propagadas. Ele produz k * TTL2 mensagens no pior caso, e ainda alcança um bom balanceamento de carga, uma vez que nenhum nó é favorecido no processo de encaminhamento de mensagens. A desvantagem é que apresenta um desempenho variável, uma vez que depende da topologia da rede e das decisões aleatórias feitas pelos “andarilhos”. Alguns exemplos de algoritmos de estratégia guiada são o BFS inteligente, o APS (Adaptative Probabilistic Search), o algoritmo baseado em ı́ndices locais, baseado em ı́ndices de roteamento e o que se fundamenta no protocolo de localização de conteúdo distribuı́do, ou DRLP (Distributed Resource Location Protocol ). O BFS inteligente [49] é um modelo “informado” do BFS modificado, pois armazena informações sobre consultas já efetuadas pelos seus vizinhos, ou através de seus vizinhos. Primeiramente, um nó identifica uma consulta similar àquela que está sendo processada, de acordo com alguma métrica de similaridade. Então o nó encaminha a consulta ao nó que retornou resultados mais “parecidos” com a consulta atual. No algoritmo APS [47], o nó armazena uma entrada para cada arquivo que ele solicitou a seus vizinhos. O valor desta entrada reflete a probabilidade de um determinado vizinho ser escolhido como próximo salto em uma consulta futura por um arquivo especı́fico. A busca é baseada em k “andarilhos” independentes e com encaminhamento probabilı́stico. Cada nó intermediário encaminha a consulta para um nó vizinho, com a probabilidade dada por sua entrada. Os valores dos ı́ndices são atualizados por feedback dos “andarilhos”. Se um deles falha, a probabilidade relativa do nó diminui, enquanto se for bem sucedido, a probabilidade relativa do nó aumenta. No algoritmo de ı́ndices locais [51], cada nó indexa todos os arquivos armazenados em nós localizados a um raio de r saltos do nó considerado, e pode responder a consultas em nome destes nós. A consulta é efetuada de uma maneira similar ao 2 O TTL é definido como o número de saltos, ou hops, como normalmente utilizado em redes peer-to-peer, e não como um espaço de tempo. 2.4. A classificação das arquiteturas de redes peer-to-peer 20 BFS, no entanto, os nós acessı́veis ao raio definido podem processar a consulta. Com isso, diminui-se o número de saltos necessários para se atingir um dado nó, reduzindo o número de mensagens propagadas na rede. O algoritmo de ı́ndices de roteamento [7] faz com que os arquivos, ou documentos, sejam divididos em categorias. Cada nó sabe um número aproximado de documentos de cada categoria que podem ser recuperados através de cada conexão. A condição de parada está sempre relacionada com um número definido de respostas. Um nó que não pode satisfazer à condição de parada com seu repositório local encaminha a mensagem para o seu vizinho que possa “melhor” satisfazer à consulta. Por fim, o algoritmo DRLP [26] estabelece que nós sem informação sobre a localização de um arquivo encaminhem a consulta para vizinhos com alguma probabilidade de resolver essa consulta. Se o arquivo é encontrado, a consulta é revertida para o requerente, armazenando a localização deste documento nos nós do caminho de volta. Em consultas futuras, nós com informações sobre a localização do recurso entram em contato diretamente com o nó indicado, sem encaminhar a mensagem pelos nós intermediários. De maneira geral, a arquitetura descentralizada não estruturada pura resulta em um sistema extremamente tolerante a falhas, pois, pouco ou nada será modificado referente ao bem-estar da rede caso um nó sofra uma falha. Uma outra caracterı́stica que essa arquitetura possui é a boa escalabilidade, em que o funcionamento do sistema não se degrada quando mais computadores são adicionados à rede. Alguns aplicativos peer-to-peer implementam essa descentralização pura, permitindo que arquivos legalmente restritos sejam compartilhados e transferidos. Em uma arquitetura descentralizada é mais difı́cil de se identificar um usuário especı́fico do que um servidor em uma arquitetura mais centralizada, o que dificulta o bloqueio da transmissão de arquivos com conteúdos protegidos e garante privacidade ao usuário, mantendo-o anônimo por não precisar se autenticar em uma entidade central. 2.4.4 Arquitetura descentralizada estruturada A arquitetura descentralizada estruturada, também referenciada por arquitetura de tabelas de hash distribuı́das, ou simplesmente DHT (Distributed Hash Table), é a 2.4. A classificação das arquiteturas de redes peer-to-peer 21 mais recente proposta para a busca de recursos em sistemas peer-to-peer. Nesta arquitetura, um número identificador é associado a cada nó da rede, e cada um deles conhece uma determinada quantia de outros nós. Quando um documento, ou arquivo, é publicado, um número identificador é associado a ele baseado em um hash do seu conteúdo, ou do seu nome. Cada nó, então, o encaminha ao nó cujo identificador é mais próximo do identificador do documento, até que se atinja o local mais adequado para o seu armazenamento. Quando um nó efetua uma consulta, a requisição é transferida até o nó com identificador mais semelhante ao do documento buscado. Esse processo continua até que uma cópia do documento seja encontrada para que seja, então, transferido ao nó que originou a requisição. Cópias desse documento podem ser feitas em cada nó que participou do roteamento, para melhorar o desempenho da busca. Apesar de ser eficiente para comunidades grandes e globais, a arquitetura baseada em DHT apresenta um problema relacionado ao identificador do documento: este identificador precisa ser conhecido antes que uma consulta seja realizada. Assim sendo, é mais difı́cil implementar um esquema de busca nesta arquitetura do que nas demais. Outra desvantagem é que, quando um documento é publicado, o algoritmo força a replicação deste documento em um nó especı́fico. Pode não ser do gosto do usuário ter de armazenar esse documento em seu espaço compartilhado. A Figura 2.8 representa esta arquitetura. Figura 2.8: Arquitetura descentralizada estruturada 2.4. A classificação das arquiteturas de redes peer-to-peer 22 Quatro principais algoritmos foram desenvolvidos para esta arquitetura: Chord [43], CAN (Content Addressable Network ) [34], Tapestry [53] e Pastry [37]. Os objetivos desses algoritmos são similares: reduzir o número de saltos necessários para encontrar um determinado arquivo e minimizar a quantidade de informação armazenada em cada nó para auxı́lio de roteamento das mensagens. 2.4.5 Arquitetura descentralizada não estruturada baseada em ultranós A arquitetura baseada em ultranós [39], ou supernós, ou, ainda, nós hub, divide a rede overlay em dois nı́veis: o nı́vel de ultranós, e o nı́vel de nós folha, como apresentado na Figura 2.9. Figura 2.9: Arquitetura descentralizada não estruturada baseada em ultranós Os ultranós vão ter a responsabilidade de atuar como servidores proxy para os demais nós, os nós folha, que estão abaixo na hierarquia da rede overlay. Estes ultranós vão “proteger” os nós folha do fluxo de mensagens, evitando, assim, a sobrecarga em nós com enlace, com memória ou com poder de processamento limitados. A eleição dos nós que devem fazer parte do nı́vel de ultranós é dependente da implementação do protocolo de camada de aplicação utilizado. O tempo de resposta, a média de tempo conectado na rede overlay, a capacidade do enlace, o poder de processamento e a quantidade de memória disponı́vel podem ser alguns dos parâmetros 2.5. Protocolos para sistemas de compartilhamento de arquivos 23 utilizados para a eleição [52]. O algoritmo GUESS [8] foi uma das primeiras propostas de implementação desta arquitetura. Nesse algoritmo uma busca é feita por meio do contato interativo com os diferentes ultranós, não necessariamente vizinhos. Como os ultranós armazenam informações de arquivos compartilhados por nós folha a ele conectados, uma única consulta apresenta alto grau de abrangência. A ordem que os ultranós são contatados não é especificada. Outro algoritmo que implementa a noção de ultranós é aplicado às redes Gnutella2 [44]. Quando um ultranó recebe uma consulta, aquele processa localmente a consulta em nome de seus nós folha e a encaminha para seus nós hub vizinhos relevantes. Os nós hub repetem o processo até que o TTL da mensagem se esgote. O nós hub que são vizinhos trocam tabelas de informações para filtrar tráfego desnecessário entre eles. A grande vantagem do uso desta arquitetura é que, por um lado, ela alcança a robustez da arquitetura cliente-servidor e, por outro, adquire a caracterı́stica descentralizada, dinâmica e anônima das arquiteturas puramente descentralizadas. A grande desvantagem é a sobrecarga que se pode causar em um nó eleito hub. No entanto, com boas polı́ticas de eleição de ultranós e de escolha do número de nós folha permitidos por nó hub, pode-se atingir um bom balanceamento de carga [52]. As redes descentralizadas não estruturadas baseadas em ultranós também podem ser implementadas sob diferentes abordagens. A abordagem cega, tal como GUESS, faz consultas aleatoriamente aos ultranós, ou seja, não usa nenhuma informação de consultas passadas para efetuar as suas. Com o uso de informações de consultas prévias, como feito pelo protocolo Gnutella2, as mensagens são enviadas a nós que possuem uma maior probabilidade de responder a uma dada consulta, evitando, assim, o envio de mensagens desnecessárias a nós com baixa probabilidade. 2.5 Protocolos para sistemas de compartilhamento de arquivos Para que os nós em uma rede overlay possam comunicar-se, um protocolo deve ser especificado. O protocolo define as regras que governam as sintaxes, semânticas 2.5. Protocolos para sistemas de compartilhamento de arquivos 24 e a sincronização da troca de informação entre os sistemas finais, e permite a conexão, comunicação e transferência de dados entre os nós na rede. Nesta seção são apresentados alguns dos protocolos mais utilizados na Internet em sistemas de compartilhamento de arquivos. 2.5.1 Gnutella O protocolo Gnutella utiliza, para a propagação das mensagens, o algoritmo BFS que utiliza a arquitetura descentralizada não estruturada pura e cega. Esse protocolo provê uma interface sob a qual os usuários podem executar consultas e ver seus resultados, e ao mesmo tempo permite que os aplicativos respondam às consultas de outros usuários. As versões 0.4 e 0.6 foram definidas e consideradas estáveis, e sua documentação é aberta [17, 18]. Pode-se dizer que o Gnutella é um dos primeiros protocolos a ser considerado puramente peer-to-peer. Foi desenvolvido pela NullSoft e foi disponibilizado sob a GPL (GNU Public License) por volta de março de 2000. A AOL (America Online) adquiriu a NullSoft após o surgimento do WinAmp e, devido a problemas com gravadoras a partir da experiência do Napster, a AOL suspendeu qualquer desenvolvimento formal do Gnutella [31]. No entanto, uma reação ocorreu por parte de usuários da Internet, que uniram esforços individuais e corporativos para inovar o Gnutella e mantê-lo funcionando. Várias versões do protocolo foram implementadas e há diversos aplicativos que o utilizam em diferentes sistemas operacionais, tais como Windows, Macintosh e Linux. A versão 0.6 do protocolo adota a eleição de alguns nós potenciais para fazer função de ultranós, ou nós hub, atuando como proxy para outros nós, chamados de nós folha, com o objetivo de diminuir o tráfego de mensagens de consulta entre nós com deficiência em termos de largura de banda, processamento e memória. Dessa maneira, o balanceamento de carga faz com que a maior parte do trabalho seja feita por nós com mais capacidade. O protocolo Gnutella define a maneira pela qual os serventes se comunicam através da rede. Ele consiste de mensagens, ou descritores, e um conjunto de regras que governam a troca de informação entre os nós, ou serventes. A versão 0.4 já 2.5. Protocolos para sistemas de compartilhamento de arquivos 25 define as cinco principais mensagens: • PING: Utilizado para descobrir nós Gnutella na rede. Um servente que recebe uma mensagem do tipo PING deve responder com uma ou mais mensagens do tipo PONG; • PONG: É a mensagem de resposta ao PING. Inclui o endereço de um servente Gnutella e informações sobre a quantidade de dados que ele disponibiliza à rede; • QUERY: É a mensagem que implementa o mecanismo de busca na rede. Um servente que recebe a mensagem do tipo QUERY deve responder com uma mensagem do tipo QUERYHIT, caso o arquivo procurado seja encontrado; • QUERYHIT: Utilizada como resposta à mensagem do tipo QUERY. Esta mensagem provê informações suficientes para que o arquivo procurado possa ser recuperado pelo servente que originou a consulta; • PUSH: Esta mensagem implementa o mecanismo que permite que serventes atrás de firewalls contribuam com seus arquivos aos demais nós. Um nó se conecta à rede Gnutella ao estabelecer uma conexão com outro já participante. A maneira pela qual os endereços de outros serventes são adquiridos não faz parte da definição do protocolo. Estabelecimento de conexão e negociação do protocolo Uma vez que o endereço IP de outro servente é obtido, uma conexão TCP é criada, e a seguinte string ASCII (American Standard Code for Information Interchange) deve ser enviada: GNUTELLA CONNECT\<vers~ ao do protocolo>\n\n O indicador de “versão do protocolo” é definido como a string (ASCII) “0.4”, nessa versão, seguidos de dois caracteres line-feed (0xA). Um servente que deseja aceitar a conexão deve responder com a seguinte mensagem: 2.5. Protocolos para sistemas de compartilhamento de arquivos 26 GNUTELLA OK\n\n Qualquer outra resposta indica que o servente não deseja efetivar a conexão. Vários motivos podem justificar esta situação, entre eles o número de conexões máximo permitido foi atingido, ou a versão do protocolo que não é suportada. As mensagens Gnutella Uma vez que dois serventes obtiveram sucesso na conexão, eles se comunicam enviando e recebendo mensagens Gnutella. Cada mensagem é precedida por um cabeçalho com o formato mostrado pela Figura 2.103 4 . Figura 2.10: Cabeçalho das mensagens Gnutella • ID da mensagem: String de 16 bytes que identifica unicamente a mensagem na rede. Este valor deve ser preservado ao se encaminhar as mensagens entre os serventes. Permite a detecção de ciclos e auxilia na redução de tráfego desnecessário; • Descritor de payload : Indica o tipo de carga que a mensagem carrega. Os valores são definidos como mostra a Tabela 2.1: Tabela 2.1: Descritores de payload do Gnutella 0x00 PING 0x01 PONG 0x80 QUERY 0x81 QUERYHIT 0x40 PUSH 3 Todas as estruturas são utilizadas com ordenação de bytes little-endian, a não ser nas exceções especificadas. 4 Todos os endereços IP das estruturas são do formato IPv4, definidos como big-endian. 2.5. Protocolos para sistemas de compartilhamento de arquivos 27 Este protocolo não define meios para o rastreamento de padrões dentro da string recebida, e como os dados são transferidos através de uma conexão TCP, que é um fluxo contı́nuo de bytes, este campo deve ser rigorosamente validado para manter o sincronismo. Uma desincronização pode ser detectada pela presença de um descritor de payload inválido; • TTL (Time to live): Indica o número máximo de encaminhamentos da mensagem permitido até que seja retirada da rede. Cada servente deve decrementar o TTL antes de a transferir para outro servente. Quando o TTL alcança o valor zero, a mensagem não pode mais ser encaminhada. Os serventes devem cuidadosamente averiguar o campo TTL e atualizá-lo adequadamente. Os abusos na utilização deste campo podem levar ao excesso de tráfego na rede; • Hops: Indica o número de vezes que uma mensagem foi encaminhada. Enquanto as mensagens são encaminhadas de servente a servente através da rede, os valores de TTL e Hops devem satisfazer os seguintes critérios: – T T L(i) + Hops(i) =T T L(0) ; – T T L(i+1) < T T L(i) ; – Hops(i+1) > Hops(i) . Os valores de T T L(i) e Hops(i) são os valores dos campos TTL e Hops no cabeçalho das mensagens no i -ésimo encaminhamento, para i >= 0; • Tamanho do payload : Representa o tamanho da mensagem imediatamente após o cabeçalho. O cabeçalho da próxima mensagem é encontrado exatamente após o número de bytes indicado por este campo. Imediatamente após o cabeçalho das mensagens há o payload especı́fico, cujo conteúdo e estrutura dependem do valor do “Descritor de payload ”. Esses descritores de payload e suas respectivas estruturas são apresentados a seguir. PING As mensagens do tipo PING não costumam possuir nenhum payload adicional e apresentam tamanho nulo, sendo simplesmente um cabeçalho com valor 0x00 no 2.5. Protocolos para sistemas de compartilhamento de arquivos 28 campo de “Descritor de payload ”. No entanto, caso houver dados após o cabeçalho, o seu payload pode ser como mostrado na Figura 2.11. Figura 2.11: A mensagem PING Um servente utiliza a mensagem PING para conseguir dados de outros nós disponı́veis na rede virtual. Ao receber esta mensagem, o servente pode responder com uma mensagem PONG, que contém o endereço de um nó Gnutella participante, podendo ser ele próprio, enviando também os dados sobre os arquivos que compartilha na rede. O campo “Dados opcionais” consiste de um número variável de bytes que são reservados para futuras extensões do protocolo. Não há a necessidade de se encaminhar mensagens PING para outros serventes, ou, ainda, encaminhá-los com altos valores de TTL e Hops. A maioria das implementações incluem uma polı́tica para limitar o tráfego de PINGs. PONG O payload da mensagem de tipo PONG é como mostrado na Figura 2.12. Figura 2.12: A mensagem PONG • Porta: Indica a porta TCP através da qual o nó pode receber conexões Gnutella; • Endereço IP: Indica o endereço IP do host; • Número de arquivos compartilhados: Representa o número de arquivos que o host disponibiliza na rede; 2.5. Protocolos para sistemas de compartilhamento de arquivos 29 • Kbytes compartilhados: Representa a quantidade, medida em Kbytes, de dados que o host disponibiliza na rede; • Dados opcionais: Um campo opcional, de tamanho variável, reservado para extensões do protocolo. As mensagens do tipo PONG são somente enviadas em resposta a uma mensagem PING. Múltiplas PONGs podem ser enviadas, permitindo, assim, que dados em cache sobre outros serventes sejam transmitidos através dos nós. QUERY A mensagem do tipo QUERY possui o payload representado pela Figura 2.13. Figura 2.13: A mensagem QUERY • Velocidade mı́nima: A velocidade mı́nima, em kbits/segundo, de serventes que respondem à essa mensagem. Um servente que recebe uma mensagem QUERY deve responder com uma QUERYHIT somente se for possı́vel comunicar-se a uma velocidade maior ou igual à indicada. • String de busca: A string de busca é encerrada por um NULL, sendo o tamanho máximo restringido pelo campo “Tamanho do payload ” presente no cabeçalho. Deve-se usar uma codificação compatı́vel com ASCII. Nenhuma delas foi especificada, mas a maioria dos serventes utilizam a ISO-8859-1 ou a UTF-8. Essa consulta deve consistir de uma lista de palavras separadas por um espaço ASCII (0x20=32) que podem, opcionalmente, carregar a extensão do formato do arquivo buscado, depois do caracter “ponto” em ASCII (0x2e=46); • NULL: O campo que encerra a string de busca; 2.5. Protocolos para sistemas de compartilhamento de arquivos 30 • Dados de consulta opcionais: Este campo é opcional e de tamanho variável, e é reservado para extensões do protocolo. Alguns serventes utilizam este campo para consultas baseadas em metadados, tal como o XML Extensible Markup Language. QUERYHIT A mensagem do tipo QUERYHIT possui o payload representado pela Figura 2.14. Figura 2.14: A mensagem QUERYHIT • Número de hits: Número de arquivos que satisfazem à consulta; • Porta: Indica a porta em que o host que responde pode receber conexões para a transferência dos arquivos; • Endereço IP: Indica o endereço IP do host; • Velocidade: Indica a velocidade máxima, em kbits/s do host; • Conjunto de resultados: Um conjunto de respostas para a QUERY recebida, contendo “Número de hits” resultados, cada uma contendo uma estrutura como representada pela Figura 2.15 Figura 2.15: A estrutura de um resultado da QUERYHIT O campo “Índice de arquivo” é um valor associado ao arquivo que o unicamente identifica dentro do conjunto de respostas enviado. O “Tamanho do arquivo” indica o tamanho do arquivo em bytes. O “Nome do arquivo” indica o nome 2.5. Protocolos para sistemas de compartilhamento de arquivos 31 do arquivo compartilhado, terminado em NULL. Os “Dados opcionais” podem conter metadados sobre o arquivo compartilhado. • Dados opcionais: Campo opcional para dados de QUERYHITs estendidos; • Identificador do servente: Este campo de 16 bytes identifica unicamente o servente na rede. Tipicamente é calculado por alguma função sobre seu endereço IP. Este identificador é instrumento para a operação efetuada pela mensagem PUSH. As mensagens do tipo QUERYHIT são enviadas somente em resposta a uma QUERY. Um servente deve responder somente se contém arquivos que podem satisfazer o critério de busca. Devem ser gerados inicialmente com o valor de Hops em zero, e o valor TTL igual ao número de Hops efetuados pela mensagem QUERY correspondente. O “ID do descritor” deve conter o mesmo valor associado à QUERY, permitindo, assim, que um servente possa rotear as QUERYHITs adequadamente. A mensagem QUERYHIT, com sua estrutura complexa, é aquela que pode apresentar o payload de maior tamanho. Para a melhor eficiência, um servente que recebe uma QUERY deve limitar a quantidade de dados que envia como resposta em uma QUERYHIT. Quando muitos hits são detectados, os serventes devem dividi-los em um subconjunto de resultados, e enviá-los separadamente com atrasos. O processo de roteamento dos QUERYHITs de volta ao servente que originou a consulta é chamado de backtrack. PUSH A mensagem do tipo PUSH possui o payload representado pela Figura 2.16. Figura 2.16: A mensagem PUSH 2.5. Protocolos para sistemas de compartilhamento de arquivos 32 • ID do servente: String de 16 bytes que identifica unicamente o servente na rede. Este servente está sendo requisitado para “empurrar” o arquivo indicado por “Índice do arquivo”. O servente que inicia o PUSH deve atribuir a este campo o mesmo valor dado ao campo “ID de servente” da mensagem QUERYHIT; • Índice do arquivo: Esse valor identifica unicamente o arquivo a ser “empurrado” pelo servente. O nó que inicia o PUSH deve atribuir a este campo o valor dado em “Índice do arquivo” de um arquivo contido no conjunto de resultados do QUERYHIT correspondente; • Endereço IP: Indica o endereço IP do host para o qual o arquivo deve ser “empurrado”; • Porta: Indica a porta do host para a qual o arquivo deve ser “empurrado”; • Dados opcionais: Este campo é reservado para futuras extensões do protocolo. Um servente deve enviar uma mensagem PUSH caso receba uma QUERYHIT de um servente que não pode receber pedidos de conexão. Isso pode ocorrer em casos de nós que estão atrás de firewalls. Quando um servente recebe uma PUSH, ele deve dar continuidade ao processo somente se o seu identificador de servente é igual ao valor contido em “ID do servente” da mensagem recebida. Roteamento de mensagens A natureza descentralizada das redes Gnutella requer que mensagens sejam roteadas através dos nós de maneira apropriada. Um servente Gnutella deve rotear as mensagens segundo os seguintes critérios: • PONGs só podem ser enviadas através do mesmo caminho que a PING correspondente atravessou. Um servente que recebe uma PONG cujo identificador não está armazenado no seu cache de identificadores de PINGs enviadas ou roteadas deve remover a PONG da rede; • QUERYHITs só podem ser enviadas através da mesma rota que a QUERY correspondente atravessou. Um servente que recebe uma QUERYHIT cujo 2.5. Protocolos para sistemas de compartilhamento de arquivos 33 identificador não está armazenado no seu cache de identificadores de QUERYs roteadas ou enviadas deve remover a QUERYHIT da rede; • PUSHs só podem ser enviadas através da mesma rota que a QUERYHIT correspondente atravessou. Um servente que recebe uma PUSH cujo identificador de servidor não está armazenado no seu cache de identificadores de servidor enviados ou roteados deve remover a PUSH da rede; • Um servente deve encaminhar PINGs e QUERYs a todos os seus nós vizinhos, exceto àquele que enviou a mensagem; • Um servente deve decrementar o campo TTL e incrementar o Hops antes de encaminhar qualquer mensagem aos nós vizinhos. Se, após decrementado, o TTL apresentar o valor zero, a mensagem não deve ser mais roteada através das conexões, sendo removida da rede; • Um servente que recebe uma mensagem com “Descritor de payload ” e “ID de descritor” iguais a algum já antes recebido, deve descartar a mensagem; As Figuras 2.17 e 2.18 apresentam exemplos de roteamento em uma pequena rede Gnutella. 2.5. Protocolos para sistemas de compartilhamento de arquivos Figura 2.17: Exemplo de Roteamento de PINGs e PONGs 34 2.5. Protocolos para sistemas de compartilhamento de arquivos Figura 2.18: Exemplo de Roteamento de QUERYs, QUERYHITs e PUSHs 35 2.5. Protocolos para sistemas de compartilhamento de arquivos 36 Para visualizar como o Gnutella trabalha, pode-se imaginar uma grande malha de nós, representando vértices de um grafo, e suas conexões representadas por arestas. O nó precisa, no inı́cio, encontrar pelo menos um outro nó já participante na rede, para poder propagar suas mensagens e rotear mensagens de outros nós. O grande problema da rede Gnutella é a maneira como seu protocolo implementa o roteamento de mensagens. Dada uma consulta, o nó envia uma mensagem do tipo QUERY a todos os nós diretamente a ele conectados. Estes nós, em seguida, processam a consulta localmente, enviando um QUERYHIT em caso de sucesso, e propagam a consulta a todos os nós a eles diretamente conectados, com exceção do nó que a enviou a. A consulta vai ser propagada de nó-a-nó até que o valor de TTL no cabeçalho dos pacotes atinja zero. Este mecanismo é chamado de query flooding e, devido à natureza broadcast da consulta, o sistema não possui uma boa escalabilidade [35]. A demanda de largura de banda cresce exponencialmente com um acréscimo linear do número de nós, portanto, aumentar o número de nós pode causar uma rápida saturação na rede. A manipulação do valor de TTL pode diminuir o número de mensagens, no entanto, implica em uma menor eficiência na busca, por atingir um número menor de nós. Na prática, a busca numa rede Gnutella é lenta e não confiável. Cada nó é um computador comum, e, portanto, estão constantemente conectando e desconectando, fazendo com que a rede nunca esteja completamente estável. Uma vez que conexões de usuários individuais são quase sempre lentas, pode-se tomar muito tempo para a consulta se transferir por grande parte da rede. Esse protocolo oferece flexibilidade no processamento das consultas pois, cada nó pode determinar como processar uma consulta e responder de acordo com ela. Por outro lado, sistemas Gnutella são muito suscetı́veis a atividades maléficas. Os nós com más intenções podem enviar muitas consultas, o que produz uma carga significantemente grande na rede. 2.5. Protocolos para sistemas de compartilhamento de arquivos 37 Download de arquivos Assim que um servente recebe um QUERYHIT, o usuário pode decidir iniciar o download de um dos arquivos presentes no “conjunto de resultados”. Os arquivos são transferidos por fora da rede Gnutella, através de uma conexão direta entre os nós. Arquivos de dados nunca são transferidos dentro da rede Gnutella. O protocolo utilizado para negociar a transferência é o HTTP. Na iniciação do download o servente que requer o arquivo deve enviar a seguinte string: GET /get/<Índice do arquivo>/<Nome do arquivo>/ HTTP/1.0\r\n User-Agent: Gnutella/0.4\r\n Range: bytes=<Offset de inı́cio>-\r\n Connection: Keep-Alive\r\n \r\n O servidor do arquivo, então, envia uma resposta também compatı́vel com o HTTP. O arquivo de dados segue esta resposta e deve ser lido por meio do socket até que se esgote o tamanho especificado na resposta do servidor. O protocolo define ainda um mecanismo para efetuar o resume de arquivos, caso haja algum problema, permitindo que a transferência seja reiniciada por algum ponto do meio do arquivo. 2.5.2 Napster O Napster é um aplicativo para o compartilhamento de arquivos, e seu protocolo, proprietário, aqui também referenciado simplesmente como Napster, é o representante mais importante das arquiteturas semi-centralizadas não intermediadas, pois utiliza um servidor central para o armazenamento da informação referente à localização dos arquivos na rede [14]. Sua tecnologia permitiu que usuários compartilhassem arquivos no formato MP3 com facilidade, e isso levou a indústria da música a impetrar massivas acusações sobre violação de direitos autorais por parte dos usuários e dos proprietários do aplicativo. Mesmo com a desativação do seu serviço original, o Napster pavimentou o caminho às redes peer-to-peer descentralizadas, que são muito mais difı́ceis de controlar. 2.5. Protocolos para sistemas de compartilhamento de arquivos 38 Shawn Fanning foi quem lançou o Napster em 1999. Sua motivação era criar um sistema para busca de arquivos de música mais fáceis de utilizar do que os disponı́veis na época, tal como o IRC (Internet Relay Chat). Isso causou um alvoroço grande o bastante para ı́cones da cena musical mundial e grandes empresas entrarem com ações judiciais contra os utilizadores e criadores do sistema [31]. Com essa nova ferramenta, a maneira pela qual as pessoas utilizavam a Internet mudou, e redes de alta velocidade em muitas universidades passaram a ser sobre-utilizadas: 80% do tráfego era para transferência de arquivos MP3 [31]. O formato de suas mensagens é proprietário, no entanto é possı́vel observar algumas operações que são utilizadas. No momento da conexão de um nó na rede, ele deve autenticar-se a um servidor central e enviar os metadados dos arquivos que disponibiliza. Os dados ficam armazenados no servidor que responde às consultas dos nós [1]. Um nó que procura um arquivo direciona-se ao servidor central enviando a consulta. O servidor responde, em caso positivo, com os endereços IP de nós conectados que podem atender àquela consulta. De posse deste endereço, o nó efetua uma conexão direta com o outro que disponibiliza o arquivo e inicia a transmissão sem a necessidade do servidor central, caracterizando aqui o modelo peer-to-peer. Sistemas que são construı́dos de acordo com essa abordagem possuem um desempenho variável, dependendo de como são implementados [50]. Apesar de apresentarem um grande nı́vel de centralização, mostram ser uma opção bastante robusta, como provado pelo Napster, registrando um pico de 26.4 milhões de usuários em 2001 [28]. 2.5.3 Gnutella2 O protocolo Gnutella2 possui documentação aberta [44], sendo um retrabalho do protocolo Gnutella, porém não reconhecido oficialmente como uma extensão do protocolo oficial. Ele elimina muitos dos aspectos do antigo protocolo Gnutella a não ser o handshake de conexão, adotando um sistema novo e completo. O protocolo Gnutella2 não é interoperável com nenhuma versão do protocolo Gnutella, no entanto utiliza a mesma arquitetura da versão 0.6: a arquitetura descentralizada não estruturada baseada em ultranós. 2.5. Protocolos para sistemas de compartilhamento de arquivos 39 A eleição de quais nós vão atuar como hub é feita com base em vários critérios: o sistema operacional que permite a abertura de vários sockets simultaneamente, a quantidade de memória RAM (Random-access memory), a velocidade de processamento, o número de horas conectados à rede, a largura de banda adequada, e a habilidade de aceitar conexões TCP e UDP sem intervenção de firewalls. As conexões TCP são utilizadas entre os nós quando eles são eleitos para formarem uma conexão permanente, em uma rede com topologia baseada em ultranós, ou hubs, fortemente conectados, servindo um denso cluster de nós folha. Estabelecer uma conexão TCP para entregar um simples pacote de informação é perda de volume de dados e tempo, especialmente quando considera-se uma grande quantidade de nós. O UDP oferece uma solução para esse caso, por ser um protocolo que apresenta um menor overhead. Porém, como o UDP é um protocolo não confiável, os pacotes podem ser perdidos ao longo da rota. O protocolo Gnutella2 resolve isto implementando uma camada de confiabilidade sobre o protocolo básico UDP. Esta camada de confiabilidade divide funcionalidades comuns ao TCP, mas não provê nenhum estado de conexão e, assim sendo, aproveita o desempenho implı́cito ao UDP. O protocolo Gnutella2, portanto, permite que haja comunicação através de três vias distintas: para um volume significante de dados, ou para o caso de uma comunicação em que dados futuros vão ser trafegados, usa-se o TCP, neste último com conexão persistente; para volumes pequenos de dados importantes, usa-se o UDP confiável; para volumes de dados pequenos de dados não muito importantes, usa-se o UDP não confiável. O protocolo Gnutella2 permite, ainda, que seja feito o swarming download, em que pedaços de arquivos podem ser obtidos, paralelamente, de diferentes hosts. Algumas mensagens que são definidas no protocolo Gnutella2 são parecidas com as mensagens do protocolo Gnutella, gerando confusão ao se levantar aspectos de interoperabilidade. 2.5.4 eDonkey Desenvolvido pela MetaMachine, o eDonkey, também chamado de eDonkey2000 ou ed2k, é um protocolo desenvolvido para o compartilhamento de arquivos, especial- 2.5. Protocolos para sistemas de compartilhamento de arquivos 40 mente de músicas, filmes e software [25]. Assim como a maioria dos protocolos de compartilhamento de arquivos, utiliza a arquitetura descentralizada: arquivos não são armazenados em um servidor central, mas são trocados diretamente pelos pares, assim como não há indexação centralizada de arquivos, e sim a utilização de ultranós. A utilização do algoritmo de hash MD4 sobre o conteúdo dos arquivos, permite uma identificação de arquivos idênticos com nomes diferentes dentro do espaço de armazenamento. O eMule foi um dos aplicativos mais populares na Internet [27]. O protocolo utilizado por esse aplicativo é baseado no eDonkey. Cada nó folha é pré-configurado com uma lista de nós hub e uma lista de arquivos compartilhados em seu sistema local, que é transmitida ao nó hub no momento de sua conexão. Uma fila é criada para cada arquivo de modo a controlar os downloads e uploads, e é possı́vel fazer downloads simultâneos de diferentes localizações para acelerar o processo e melhorar o balanceamento de carga. É possı́vel também disponibilizar pedaços de arquivos que sequer tenham terminado seu download completamente. O protocolo utilizado pelo eMule estende as capacidades do protocolo eDonkey permitindo que os nós folha troquem informações sobre os nós hub, sobre outros nós folha e sobre arquivos [19]. Os nós hub, usualmente, não armazenam arquivos, mas, sim, informações sobre os arquivos localizados nos nós folha. Outra funcionalidade destes nós é intermediar conexões entre nós folha que estão atrás de firewalls e não podem receber pedidos de conexão. Essa intermediação de conexões acrescenta uma grande carga nos nós hub. Faz parte do protocolo um sistema de créditos que estimulam o usuário a compartilhar mais arquivos, aumentando, assim, a largura de banda disponı́vel a um determinado nó para fazer downloads. Esse recurso é útil para eliminar os chamados freeloaders, que utilizam recursos da rede virtual sem prover recursos [19]. 2.5.5 FastTrack O FastTrack é um protocolo proprietário. No entanto, algumas partes de sua implementação são conhecidas [16]. Em sua organização, os nós são divididos em nós folha e ultranós. Ultranós são responsáveis por indexar os arquivos que os nós folha 2.5. Protocolos para sistemas de compartilhamento de arquivos 41 compartilham e também fazem trabalhos de estatı́sticas. Os mecanismos de conexão e de distribuição de responsabilidades são similares ao protocolo Gnutella2. A fase de transferência de arquivos é negociada utilizando o protocolo HTTP. Os nós folha não se comunicam uns com os outros, a não ser para a transferência de arquivos. O aplicativo Kazaa, muito utilizado na Internet, utiliza a porta 1214 para efetuar as transferências das mensagens. No entanto, versões recentes utilizam portas aleatórias, e algumas escutam na porta 80. Alguns formatos de pacotes e seus tipos são conhecidos, mas pouco é sabido sobre a comunicação entre ultranós. Alguns aplicativos que implementam esse protocolo utilizam um sistema de “reputação”, que visa incentivar os usuários a compartilhar arquivos e permitir que uploads sejam efetuados. Inicia-se com nı́vel de participação de valor dez, e pode-se atingir o valor 1000. Um maior nı́vel de participação significa que o usuário está conectado por grandes perı́odos de tempo e permitiu que usuário tirassem proveito disso, obtendo seus arquivos. Usuários com maiores nı́veis de participação são favorecidos em filas de espera de download e recebem melhor QoS (Quality of Service). 2.5.6 Kademlia O protocolo Kademlia [24] implementa a arquitetura descentralizada estruturada. Um identificador é associado a cada nó da rede, e um valor a cada documento. Cada participante possui uma chave de identificação de 160 bits, e cada documento será roteado até o nó de melhor posição dentro do sistema. Os pares (chave, documento) são armazenados em nós com identificadores mais “próximos” à chave, para se ter a noção de proximidade. O Kademlia visa a minimizar o número de mensagens que os nós têm que enviar uns aos outros para adquirir informações sobre a rede. A informação de configuração é espalhada automaticamente como efeito das buscas, e os nós possuem conhecimento e flexibilidade para rotear consultas através de caminhos de menor latência. Um nó que deseja entrar na rede deve antes passar por um processo de iniciação. Nesta fase, o nó deve descobrir o endereço IP de outro nó, obtido diretamente pelo usuário, ou por uma lista armazenada, que já está participando da rede Kademlia. 2.5. Protocolos para sistemas de compartilhamento de arquivos 42 Um identificador randômico ainda não utilizado é calculado e atribuı́do ao novo nó. O algoritmo do Kademlia é baseado no cálculo da “distância” entre dois nós. Essa distância é calculada como um “ou exclusivo” de dois identificadores, tendo como resultado um número inteiro. A “distância” não possui relações com condições geográficas, mas designa a distância dentro da faixa de identificadores. Com isso, pode ocorrer o caso de um nó na Alemanha e um nó na Austrália serem vizinhos dentro do sistema. O número de nós contactados durante uma busca é dependente do tamanho da rede. Se o número de participantes na rede dobra, então um nó requerente deve consultar somente um nó a mais por busca, e não duas vezes mais, provando a boa escalabilidade do sistema. Algumas vantagens ainda podem ser encontradas na redes descentralizadas estruturadas, que claramente aumentam a resistência contra ataques DoS (Denial of Service). Mesmo que um conjunto de nós forem atacados, isso implicará um pequeno impacto na disponibilidade da rede, que se recuperará, pois o algoritmo faz com que estes “buracos” sejam supridos por outros nós. Uma vez que não há uma instância central para armazenar os ı́ndices dos arquivos existentes, essa tarefa é dividida de maneira igual através de todas as entidades. O nó que deseja compartilhar um arquivo efetua um processamento, aplicando um hash no conteúdo do arquivo que, como resultado, vai identificá-lo dentro da rede de compartilhamento. Por isso, o resultado dos hashes e os identificadores dos nós devem possuir o mesmo tamanho, garantindo a compatibilidade entre os valores. Para efetuar uma consulta, busca-se na rede o nó cujo identificador tem a menor distância do hash do arquivo, com a utilização da lista de contatos que está armazenada no nó local. Os contatos armazenados na rede estão sob constante mudança pois os nós conectam e desconectam aleatoriamente. A replicação das informações a respeito da localização de nós faz com que o desempenho da busca aumente. Com isso, quando um contato não está na lista local, o nó consulta o nó mais próximo daquele que ele procura afim de recuperar o endereço final. O Kademlia é utilizado pelo programa de compartilhamento Morpheus. Outros protocolos possuem a mesma natureza, com variações em desempenho quando analisados sob a perspectiva do número de mensagens que requerem para uma busca, 2.5. Protocolos para sistemas de compartilhamento de arquivos 43 tempo de configuração de um novo nó, ou quantidade de dados que os nós devem armazenar para efetuar o roteamento das mensagens. A Tabela 2.2 mostra uma tabela que resume alguns protocolos utilizados por aplicativos de compartilhamento de arquivos na Internet, mostrando a arquitetura de rede peer-to-peer utilizada por cada um deles. Tabela 2.2: Exemplos de protocolos de compartilhamento de arquivos e suas arquiteturas Protocolo Arquitetura Gnutella Descentralizada pura Gnutella2 Baseada em ultranós Freenet Estruturada Napster Semi-centralizada não intermediada Kademlia Estruturada eDonkey Baseada em ultranós FastTrack Baseada em ultranós BitTorrent Descentralizada pura Chord Estruturada Pastry Estruturada A escolha de quais arquiteturas e protocolos devem ser utilizados depende das caracterı́sticas do aplicativo e do ambiente em que será executado. Deve-se considerar todos os requisitos necessários de um aplicativo peer-to-peer, desde o seu desenvolvimento, até a sua implantação, levando-se sempre em consideração o seu desempenho para a sua especificidade. Capı́tulo 3 Os protocolos da camada de transporte Os procedimentos utilizados para a implementação dos mecanismos que permitem a troca de dados entre os elementos de uma rede de computadores são complexos. Para reduzir esta complexidade, tarefas são divididas em camadas; cada camada executa um subconjunto de funções, utilizando serviços da camada inferior, e provendo serviços à camada superior. Os protocolos implementam os serviços que uma determinada camada oferece, definindo os tipos e a sintaxe das várias mensagens, a semântica de seus campos e as regras que determinam quando essas mensagens são enviadas e respondidas. A camada de transporte é situada entre a camada de aplicação e a camada de rede da arquitetura Internet, e tem como função principal oferecer uma “comunicação lógica” entre processos de aplicação em diferentes hospedeiros. Isso significa que, embora os processos não estejam fisicamente conectados, do ponto de vista da aplicação, é como se o estivessem. A Figura 3.1 ilustra essa “comunicação lógica”, destacando as pilhas de protocolos utilizadas em uma comunicação entre dois processos, em dois hosts diferentes. Os protocolos da camada de transporte mais utilizados na Internet são o UDP (User Datagram Protocol ) e o TCP [45]. Um outro procolo da camada de transporte é o SCTP. Este capı́tulo tem como objetivo mostrar as caracterı́sticas e os serviços oferecidos por esses protocolos, e efetuar uma comparação entre eles. 44 3.1. O protocolo UDP 45 Figura 3.1: Comunicação lógica entre dois processos, em dois hosts diferentes 3.1 O protocolo UDP O UDP [32] é um protocolo de transporte simples, leve, com um modelo de serviço que apresenta as funções mı́nimas da camada de transporte: multiplexação, demultiplexação e verificação de erros. Os dados da camada de aplicação, quando empacotados pelo UDP, são referenciados por datagrama. O seu cabeçalho é como mostra a Figura 3.2. Figura 3.2: Cabeçalho do protocolo UDP Os campos “porta de origem” e “porta de destino”, de 16 bits cada um, identificam os processos nos sistemas finais que enviam e recebem os datagramas. Uma vez que o UDP é um protocolo que não armazena estados, a “porta de origem” pode ser opcional, pois um computador que envia dados pode não solicitar respostas. Se 3.2. O protocolo TCP 46 não utilizado, o valor deste campo deve ser configurado com o valor zero. O campo “tamanho” designa o comprimento do datagrama em bytes, incluindo ambos o cabeçalho e o campo de dados. O tamanho mı́nimo de um datagrama é oito bytes, sendo, neste caso, o campo de dados vazio. O campo “checksum” carrega a soma de verificação utilizada na detecção de erros no cabeçalho e nos dados transmitidos. É um protocolo orientado à mensagem, não orientado à conexão e não confiável, isto é, não garante que os pacotes sejam entregues ao destinatário e nem que cheguem de forma ordenada. No entanto, se o pacote chegar ao destinatário, o checksum permite a verificação da existência de erros. As aplicações construı́das sobre o UDP devem tratar perdas, erros, duplicação e desordenação na entrega de datagramas. No entanto, muitas destas aplicações se adaptam melhor ao UDP pois: • Não há estabelecimento de conexão; • Não há armazenamento de estados de conexão; • Possui pouco overhead no cabeçalho do pacote; • Possui taxa de envio não regulada. O UDP não necessita de estabelecimento de conexão, pois nenhuma variável necessita ser configurada no remetente, ou no destinatário. Assim sendo, é um protocolo que não armazena estado de conexão, ou seja, não necessita de variáveis para armazenar detalhes sobre a troca de dados entre sistemas finais. O seu cabeçalho é pequeno, sendo considerado lightweigth, ou seja, adiciona pouco overhead em relação ao campo de dados. A sua taxa de envio não é regulada, não havendo mecanismos para a detecção de transbordamento de buffer do destinatário ou de congestionamento na rede. 3.2 O protocolo TCP O TCP oferece outros serviços além da multiplexação, demultiplexação e verificação de erros que o UDP oferece. Esse protocolo oferece o serviço de entrega de dados 3.2. O protocolo TCP 47 confiável e ordenado. É um protocolo orientado à conexão e a bytes, e ainda implementa o controle de fluxo e de congestionamento. Os dados da camada de aplicação empacotados com o cabeçalho do TCP é denominado segmento. O seu cabeçalho pode ser representado pela Figura 3.3. Figura 3.3: Cabeçalho do protocolo TCP Os campos “porta de origem” e “porta de destino”, assim como no UDP, determinam a quais processos encaminhar os dados nos sistemas finais. Os “número de seqüência” e “número de reconhecimento” são utilizados para a implementação do serviço confiável de transferência de dados. O campo “tamanho do cabeçalho” designa o tamanho do cabeçalho do pacote em palavras de 32 bits. O campo “flags” contém 6 bits, que são utilizados para identificar inı́cio e fim de conexão, assim como entrega de dados urgentes, entre outras finalidades. O campo “janela” é utilizado para o controle de fluxo. O campo “checksum”, como no protocolo UDP, é utilizado para a verificação de erros no cabeçalho e nos dados do usuário, e o campo “ponteiro urgente” auxilia na entrega de dados urgentes à camada de aplicação. O campo “opções” é facultativo, e dentre outras funções, pode ser utilizado para definir o MSS (Maximum Segment Size) entre os sistemas finais. Para efetuar uma conexão, o TCP implementa o chamado 3-way handshake. Este mecanismo executa a troca de três mensagens entre os hospedeiros, que faz com que buffers sejam criados para armazenar dados durante o tempo de vida da conexão. 3.2. O protocolo TCP 48 São definidos, nesta fase, os número de seqüência iniciais, sendo J o do cliente, e K o do servidor, como mostra a Figura 3.4. Figura 3.4: Iniciação de conexão TCP O cliente efetua um pedido de conexão (active open) enviando um segmento TCP do tipo SYN, e define o número de seqüência inicial para os dados que enviar. Normalmente não há dados do usuário com este segmento; somente o cabeçalho IP e TCP, e possı́veis opções do TCP. O servidor responde então com um ACK, enviando também seu número de seqüência inicial. O cliente, então, responde com um SYN, e a conexão está estabelecida. Durante o tempo de vida de uma conexão TCP, o protocolo que roda em cada um dos hospedeiros faz transições por vários estados TCP. A Figura 3.5 mostra estes vários estados, desde o estabelecimento até o encerramento da conexão1 . Há 11 diferentes estados para uma conexão TCP, e as regras do protocolo ditam como as transições acontecem. Por exemplo, se uma aplicação está no estado CLOSED e decide conectar-se a um servidor (active open), o TCP envia um SYN e passa para o estado SYN SENT. Se recebe em seguida um SYN conjugado com um ACK, envia um ACK, e o novo estado passa a ser ESTABLISHED. É neste estado que a maioria das transferência dos dados ocorrem. Enquanto o TCP troca três mensagens para estabelecer uma conexão, ele troca quatro para encerrá-la, como mostra a Figura 3.6. O lado que recebe um FIN efetua 1 Na figura apresentada, duas transições são omitidas: iniciação simultânea, em que SYNs percorrem a rede em sentidos opostos ao mesmo tempo, e encerramento simultâneo de conexão, em que FINs percorrem a rede em sentidos opostos ao mesmo tempo. 3.2. O protocolo TCP 49 Figura 3.5: Estados de uma conexão TCP o encerramento passivo (passive close), sendo, então, enviado um reconhecimento ao FIN. O recebimento de um FIN quer dizer que dados não serão mais recebidos através da conexão. Algum tempo depois, a aplicação que recebeu o FIN também decide encerrar a sua conexão. Isso faz com que o TCP envie um FIN ao outro par, que envia o respectivo reconhecimento. O TCP vê os dados como uma cadeia de bytes desestruturada, mas ordenada. O uso que o TCP faz dos número de seqüência reflete essa visão, pelo fato de que esses números são aplicados sobre a cadeia de bytes transmitidos, e não sobre a série de segmentos. Do ponto de vista da aplicação, um fluxo de bytes é “empurrado” através do socket, no qual uma determinada quantidade de bytes de dados vai ser enviada ao destinatário. O controle de fluxo é o mecanismo que os sistemas finais utilizam para evitar que 3.2. O protocolo TCP 50 Figura 3.6: Encerramento de conexão TCP os dados enviados transbordem a capacidade do buffer de seus destinatários. Para isso, o campo do cabeçalho “janela” é utilizado. A janela de recepção é utilizada para dar ao remetente uma idéia de quanto espaço livre de buffer está disponı́vel no destinatário. Na conexão do TCP, que é full-duplex, cada remetente mantém uma janela de recepção distinta, que varia dinamicamente durante o tempo de vida útil da conexão. A cada segmento que cada remetente envia ao outro, o campo “janela” é atualizado, informando o outro par sobre quantos bytes estão disponı́veis a receber, sem encontrar problemas na utilização de seu buffer. O controle de congestionamento, diferentemente do controle de fluxo, visa ao bem-estar geral da rede, e não somente ao do destinatário. O congestionamento é causado, principalmente, pelo estouro na capacidade dos buffers dos roteadores da rede, em que demasiadas fontes estão tentando enviar dados a uma taxa muito alta. O TCP utiliza para o controle de congestionamento um algoritmo chamado “aumento-aditivo-diminuição-multiplicativa”, ou AIMD (Additive-increasemultiplicative-decrease) [20]. O TCP é utilizado quando a aplicação necessita de confiabilidade sobre o serviço não confiável da rede IP. No entanto, em alguns casos, o TCP não provê as exatas funcionalidades necessárias à aplicação, ou provê mais que o necessário. No primeiro caso a aplicação necessita fazer um trabalho extra para utilizar o TCP, enquanto no último, a funcionalidade extra do TCP pode ser um entrave [33]. 3.3. O protocolo SCTP 3.3 3.3.1 51 O protocolo SCTP Histórico do SCTP O desenvolvimento do SCTP foi motivado pela necessidade de sanar as deficiências do TCP e do UDP ao lidar com algumas aplicações, em especial aquelas de telefonia sobre a rede IP. Em princı́pio, seu desenvolvimento visava a um protocolo que operava sobre o UDP, oferecendo confiabilidade sobre um protocolo da camada de transporte não confiável [41]. Muitas implementações foram feitas até que se chegasse ao precursor do SCTP, chamado MDTP (Multi-Network Datagram Transmission Protocol ). Assim que a primeira implementação utilizável foi concluı́da, seus autores a submeteram para a IETF (Internet Engineering Task Force). Uma iniciativa da IETF relativa à telefonia sobre IP estava sendo desenvolvida paralelamente a essa submissão pelo grupo SIGTRAN (Signaling Transport Working Group). Ao analisar o protocolo MDTP, o SIGTRAN considerou seus conceitos interessantes e propôs várias modificações no protocolo, a fim de melhorar e refinar seu desenvolvimento original. Durante esse refinamento, o nome do protocolo mudou de MDTP para SCTP, como sinal de expansão de escopo e de funcionalidade do protocolo. Essa expansão permitiu que o SCTP passasse a ser considerado um protocolo da camada de transporte, ao invés de um protocolo atuando sobre o UDP, sendo assim utilizado diretamente sobre a camada de rede. 3.3.2 Caracterı́sticas do SCTP O SCTP é um protocolo que, como o TCP e o UDP, pertence à camada de transporte da pilha de protocolos da arquitetura TCP/IP [30, 41]. É um protocolo confiável, orientado à conexão e à mensagem. Implementa o controle de fluxo e de congestionamento, e pode ser usado concorrentemente com o TCP, sem falhas na justiça de utilização do canal de dados. Apresenta funcionalidades adicionais em relação ao TCP e ao UDP, tais como o multi-homing, os multi-fluxos e entrega desordenada2 2 O termo “desordenada” aqui proposto, e doravante usado no decorrer do texto, está relacionado com mensagens do SCTP, e refere-se ao tratamento de entrega das mensagens sem considerar a ordem ou 3.3. O protocolo SCTP 52 das mensagens, que torna este protocolo mais leve e, portanto, mais adequado para determinados tipos de aplicações. Os dados da camada de aplicação empacotados com o cabeçalho do protocolo SCTP são denominados simplesmente pacotes. Os campos desse cabeçalho são mostrados na Figura 3.7. Nas redes IP, esses pacotes são encapsulados no payload do pacote IP. Um pacote SCTP é formado por um cabeçalho comum e chunks. Múltiplos chunks podem ser multiplexados em um só pacote, até o limite definido pelo MTU (Maximum Transfer Unit). Um chunk pode conter dados de controle ou dados do usuário. Figura 3.7: Formato do pacote SCTP O cabeçalho comum consiste de 12 bytes. Para a multiplexação e demultiplexação o SCTP utiliza os mesmos mecanismos do TCP e UDP, a partir dos campos “porta de origem” e “porta de destino”. Para a detecção de erros de transmissão, cada pacote SCTP é protegido com um valor de soma de verificação de 32 bits (Adler-32 algorithm). O cabeçalho comum possui ainda um campo chamado “Verification tag”. Esse campo é especı́fico para cada associação SCTP3 , e eles são trocados entre os sistemas seqüência. 3 No SCTP o relacionamento entre os pares é denominado associação, em vez de conexão, como no TCP, 3.3. O protocolo SCTP 53 finais no momento do estabelecimento da associação. Cada chunk possui um campo de “tipo”, para diferenciar chunk de dados e os vários chunks de controle, seguidos de campos de flags, e de comprimento, este último utilizado para definir o tamanho do chunk. O chunk de dados deve apresentar um formato especı́fico, como mostra a Figura 3.8. O campo “tipo” deve conter o valor “0x00”, referente ao chunk de dados de usuário. Os flags U, B e E indicam respectivamente, quando configurados com o valor 1, chunk a ser entregue sem ordenação, chunk de inı́cio de mensagem e chunk de fim de mensagem. Figura 3.8: Chunk de dados do SCTP O campo TSN (Transmission Sequence Number ) é utilizado pelo remetente e pelo destinatário para identificação dos chunks enviados e recebidos, como auxiliar para retransmissões e detecção de duplicação, assim como para a fragmentação e remontagem de mensagens. O campo “identificador de fluxo” indica para qual fluxo o dado do usuário é destinado. O campo “número de seqüência de fluxo”, associado ao “identificador de fluxo”, identifica a ordem de entrega da mensagem dentro de um fluxo especı́fico. O campo “identificador de payload ” é ignorado pelo SCTP. No entanto, o usuário pode querer usá-lo para determinar qual tipo de dado está sendo carregado dentro da mensagem. Este valor pode ser utilizado também por monitores de rede e filtros de pacotes. pois pode utilizar um número arbitrário de vias de comunicação, denominadas fluxos, como explicado adiante neste capı́tulo. 3.3. O protocolo SCTP 54 Outros tipos de chunks são definidos para controle da associação, tais como chunks para inicialização e encerramento de associação e reconhecimento seletivo, e podem ser identificados ao se variar o valor do campo “tipo” do cabeçalho do chunk. Alguns deles são apresentados na Tabela 3.1. Tabela 3.1: Tipo do Chunk DATA INIT INIT-ACK COOKIE-ECHO COOKIE-ACK SACK HEARTBEAT HEARTBEAT-ACK ABORT ERROR SHUTDOWN SHUTDOWN-ACK SHUTDOWN-COMPLETE Os chunks do SCTP Valor Utilização 0x00 Dados do usuário 0x01 Iniciação de associação 0x02 Iniciação de associação 0x0a Iniciação de associação 0x0b Iniciação de associação 0x03 Reconhecimento seletivo 0x04 Keep-alive 0x05 Keep-alive 0x06 Encerramento de associação 0x09 Relatório de erros 0x07 Encerramento de associação 0x08 Encerramento de associação 0x0c Encerramento de associação O estabelecimento de associação do SCTP4 é similar ao estabelecimento de conexão do TCP, exceto pela utilização de quatro mensagens (4-way handshake) e pela utilização de um cookie. O INIT carrega, junto com outros parâmetros, um tag de verificação, Ta, e um número de seqüência inicial, J. O tag Ta deve estar presente em todos os pacotes enviados pelo par durante o tempo de vida da associação. O outro par também escolhe um tag de verificação, Tz, que deve estar presente em todos os seus pacotes enviados. Junto a este tag e ao número de seqüência inicial K, o receptor do INIT também envia um cookie C. Este cookie contém todos os estados necessários para se configurar uma associação SCTP, para que o servidor não precise armazenar informações sobre o cliente que está efetuando a associação. A parte passiva da associação não aloca recursos para a associação até que a terceira mensagem tenha chegado, e tenha sido validada. Esse mecanismo visa a solucionar os problemas de ataques DoS. A Figura 3.9 representa a iniciação de uma associação SCTP. O SCTP é um protocolo que implementa a noção de estados. As transições de 4 Este estágio pode, também, ser referenciado por estabelecimento de conexão 3.3. O protocolo SCTP 55 Figura 3.9: Iniciação de uma associação SCTP um estado a outro são ditadas pelas regras do protocolo, baseados no estado atual e nos chunks recebidos. Por exemplo, se uma aplicação está no estado CLOSED e decide efetuar uma conexão (active open), ela envia uma mensagem INIT ao lado servidor e passa para o estado COOKIE-WAIT. Se, a partir daı́, receber um INITACK, envia um COOKIE-ECHO e seu novo estado é COOKIE-ECHOED. Se recebe um COOKIE-ACK, passa para o estado ESTABLISHED, estado no qual a maioria das transmissões de dados são efetuadas. Esses estados são apresentados na Figura 3.10. O SCTP não apresenta os estados half-closed e TIME WAIT do TCP, este último devido à utilização do tag de verificação. Assim sendo, para o encerramento de associação são trocadas somente três mensagens, como mostra a Figura 3.11. Entre os vários serviços de transporte oferecidos pelo SCTP, de suma importância é a provisão de múltiplos fluxos dentro uma associação. Os mecanismos de confiabilidade na transmissão de dados, tais como a detecção de perda de dados e as retransmissões, é separado do mecanismo de ordenação de mensagens, este último implementado com o auxı́lio da funcionalidade de multi-fluxos. Essa funcionalidade permite que dois sistemas finais transmitam duas ou mais seqüências separadas de mensagens, sem introduzir dependência entre elas. Cada seqüência, ou fluxo de mensagens, será ordenada, mas as mensagens em diferentes fluxos não interferem na ordem de entrega umas das outras. O SCTP opera, portanto, em dois nı́veis. O primeiro nı́vel é considerado extrafluxo e o segundo nı́vel é considerado intra-fluxo. Dentro de uma associação, a 3.3. O protocolo SCTP 56 Figura 3.10: Estados de uma associação SCTP transmissão confiável de pacotes é atingida pela utilização da soma de verificação, os número de seqüência, os temporizadores e o mecanismo de retransmissão seletiva. Cada chunk corretamente recebido é encaminhado ao segundo nı́vel de operação do protocolo. O segundo nı́vel é responsável por realizar a ordenação parcial dos datagramas. Isto é, a ordem é mantida entre cada fluxo, mas não entre diferentes fluxos. O número de fluxos em cada sentido é configurado na iniciação da associação. Uma outra funcionalidade que o SCTP oferece é o suporte a nós multi-endereçados (multi-homing). Com esta funcionalidade, os nós configurados com diferentes interfaces de rede podem ser alcançados através de diferentes endereços IP, com a utilização de uma única associação. Assim sendo, os dados podem trafegar através 3.3. O protocolo SCTP 57 Figura 3.11: Encerramento de conexão SCTP de diferentes caminhos fı́sicos, podendo aumentar o desempenho e a tolerância a falhas. As informações a respeito dos múltiplos endereços são trocadas no momento do estabelecimento da associação. Um dos endereços é selecionado como o caminho primário, sobre os quais os pacotes vão ser transmitidos predefinidamente. No entanto, retransmissões podem ser feitas por um dos caminhos disponı́veis. Se um dos caminhos falha, o outro pode ser utilizado para efetuar a transferência dos dados. A funcionalidade do multi-homing pode ser representada pela Figura 3.12, que ilustra dois computadores se comunicando através de diferentes meios fı́sicos, em que cada interface de rede é atingida por um diferente endereço IP. Figura 3.12: Caracterı́stica de multi-homing do SCTP O SCTP utiliza um mecanismo de controle de fluxo baseado em janela de buffer, similar àquele utilizado pelo TCP. O receptor especifica uma janela de recepção, e retorna o seu tamanho corrente através dos chunks do tipo SACK. O controle de congestionamento do SCTP derivou do controle de congestionamento do TCP, com algumas modificações feitas para o multi-homing. Para cada 3.4. A comparação entre o UDP, o TCP e o SCTP 58 endereço de destino, um conjunto de parâmetros para controle de fluxo e de congestionamento é mantido. Desta maneira, uma associação SCTP com mais de um caminho se comporta de maneira similar a um mesmo número de conexões TCP. O mecanismo de controle de congestionamento similar ao do TCP assegura que o SCTP pode ser introduzido sem problemas na rede onde o TCP é amplamente utilizado. 3.4 A comparação entre o UDP, o TCP e o SCTP Para efetuar a comparação entre os protocolos UDP, TCP e SCTP, primeiramente são levantadas as limitações do UDP e do TCP. Em seguida, são apresentadas as semelhanças e as diferenças especı́ficas entre os protocolos TCP e SCTP, que são dois protocolos orientados à conexão. 3.4.1 Limitações do UDP Como descrito anteriormente, o UDP é um protocolo simples que oferece os serviços mı́nimos de um protocolo da camada de transporte. A partir disto, algumas das limitações do UDP podem ser levantadas [42]: • O UDP não oferece o serviço de transferência confiável e ordenada de dados. Por isso, a aplicação que envia os dados sobre UDP não pode ter certeza se o nó de destino recebeu os seus datagramas, e também não sabe a ordem exata em que foram entregues; • O UDP não implementa mecanismos de controle de fluxo. Com isso, uma aplicação pode transbordar os buffers de recebimento do nó destinatário; • O UDP não implementa o controle de congestionamento. Os dados enviados por uma rede congestionada, sem o controle de congestionamento, afetam os dados enviados por outras aplicações, sendo descartados com maior probabilidade. Com essas limitações, o UDP não pode encontrar o nı́vel de confiabilidade necessária a algumas aplicações. No entanto, pelo fato de o UDP ser orientado à mensagem, e ser considerado, em geral, um protocolo que introduz pouco overhead, 3.4. A comparação entre o UDP, o TCP e o SCTP 59 houve várias tentativas de introduzir na camada de aplicação os serviços não oferecidos pela camada de transporte. Neste caso a aplicação precisa implementar seus próprios mecanismos para oferecer os seguintes serviços: • Detecção e retransmissão de mensagens perdidas; • Correção de mensagens fora de ordem e duplicadas; • Detecção e soluções para o congestionamento de redes. A implementação desses serviços na camada de aplicação pode não ser a melhor solução, pois são mecanismos com alto grau de complexidade de desenvolvimento. Além disso, os serviços citados são normalmente oferecidos por outros protocolos da camada de transporte, e desenvolvê-los na camada de aplicação pode ser considerado um trabalho redundante. 3.4.2 Limitações do protocolo TCP Como descrito anteriormente, o TCP é um protocolo que oferece confiabilidade, é orientado à conexão, e oferece a entrega ordenada dos dados. No entanto, pode apresentar algumas limitações ao ser utilizado em determinados tipos de aplicações [42]: • O TCP oferece ordenação estrita na entrega dos dados. Alguns aplicativos não necessitam deste serviço, pois permitem a ordenação parcial na entrega de informações, e a ordenação estrita poderia, neste caso, acrescentar atrasos indesejáveis. A perda de um segmento TCP pode bloquear a entrega de todos de segmentos subseqüentes já disponı́veis no destinatário, até que o segmento perdido seja retransmitido e entregue. Este fenômeno é chamado de headof-line blocking, ou HOL, e será discutido em maiores detalhes no próximo capı́tulo; • Muitas aplicações necessitam de entrega confiável de “mensagens”, mas o TCP é um protocolo que controla a sua transmissão de dados baseados em bytes. Aplicações orientadas à mensagem alcançam essa funcionalidade delineando o fluxo de bytes, ao adicionar seus próprios registros de limites; 3.4. A comparação entre o UDP, o TCP e o SCTP 60 • O TCP não possui suporte para hosts com múltiplas interfaces, os chamados, multi-homed hosts. Isso traz uma imensa dificuldade a desenvolvedores que desejam construir a redundância de enlace; • O TCP é conhecido por ser relativamente vulnerável a alguns ataques DoS, tais como SYN flooding. Com essas limitações, o TCP pode ser um protocolo pouco adequado para determinados tipos de aplicações, tais como aquelas que necessitam da orientação às mensagens, e que não precisam da ordem estrita na entrega dos dados. 3.4.3 SCTP versus TCP versus UDP O SCTP foi desenvolvido para sanar as limitações do TCP e UDP. Seu desenvolvimento foi motivado diretamente pelo transporte de mensagens da PSNT (Public Switched Telephone Network ) sobre a rede IP. No entanto, aplicações que necessitam de funcionalidades iguais ou semelhantes às da PSNT podem utilizar este protocolo [6]. A Tabela 3.2 mostra um resumo das caracterı́sticas e serviços do SCTP, do TCP e do UDP [42], já citadas no decorrer deste capı́tulo. Tabela 3.2: Comparação entre o SCTP, o TCP Caracterı́stica do protocolo SCTP Estados armazenados nos sistemas finais sim Transferência confiável de dados sim Controle de congestionamento sim Delimitação de limites das mensagens sim Descobrimento do MTU do caminho sim Fragmentação e remontagem de informação sim Multiplexação de informação no pacote sim Suporte a multi-homing sim sim Entrega desordenada de dados Segurança contra SYN flooding sim Heartbeat sim e o UDP TCP UDP sim não sim não sim não não sim sim não sim não sim não não não não sim não não não não A partir da Tabela 3.2 observa-se que o TCP é mais parecido com o SCTP do que o UDP. Como ambos os protocolos TCP e SCTP são orientados à conexão e apresentam propósitos parecidos, há algumas semelhanças e diferenças entre eles, como descritas nas seções a seguir. 3.4. A comparação entre o UDP, o TCP e o SCTP 3.4.4 61 As similaridades entre o SCTP e o TCP • Iniciação: Ambos os protocolos passam por uma fase de trocas de mensagens iniciais para configurar o relacionamento fim-a-fim. Há diferenças nos formatos dessas mensagens e na maneira pela qual são trocadas. No entanto, essa fase possui o mesmo propósito em ambos os protocolos, que é o de estabelecer uma conexão, ou associação; • Confiabilidade: O SCTP e o TCP oferecem mecanismos para o envio de dados de maneira confiável sobre a rede IP, que não é orientada à conexão e não oferece confiabilidade; • Ordenação: Ambos os protocolos oferecem o serviço de entrega ordenada de mensagens. Algumas aplicações necessitam de ter certeza de que suas mensagens estão sendo entregues na ordem correta, tal como uma aplicação que efetua funções de inclusão de dados em uma base remota, e em seguida deleta estes dados. Resultados inesperados podem ocorrer caso as mensagens cheguem em ordem não prevista. • Controle de congestionamento: Outra importante similaridade é a provisão de controle de congestionamento. Ambos o SCTP e o TCP oferecem o mesmo mecanismo de controle de congestionamento, baseado no AIMD. Sem esse controle, a Internet poderia ceder seus serviços pelo excesso de tráfego imposto pelos sistemas finais. Uma vez que os dois protocolos utilizam o mesmo algoritmo de congestionamento, há a garantia de que haverá justiça na utilização da largura de banda quando eles forem utilizados simultaneamente sobre a mesma rede; • Encerramento: Ambos os protocolos oferecem duas maneiras de encerrar seu relacionamento, denominadas “maneira elegante” e “maneira não elegante”. A maneira elegante pode ser utilizada para assegurar ao usuário que todos os dados na fila de envio, no momento do encerramento, vão ser entregues confiavelmente ao nó destinatário, antes que o relacionamento fim-a-fim seja terminado. A maneira não elegante, por sua vez, pode acontecer em casos de encerramentos inesperados, onde dados podem ser perdidos, por não haver o esvaziamento das filas de envio. 3.4. A comparação entre o UDP, o TCP e o SCTP 3.4.5 62 As diferenças entre o SCTP e o TCP • Diferenças na inicialização: Como mencionado, o TCP e o SCTP trocam mensagens de configuração antes de iniciar sua comunicação fim a fim. No entanto, o SCTP diferencia-se do TCP por utilizar quatro vias, ou mensagens, em contraste com a iniciação de três vias do TCP. A justificativa para a utilização de quatro vias na iniciação é a proteção contra ataques de SYN flooding; • Head-of-line blocking: Há uma maneira diferenciada pela qual o SCTP permite que dois sistemas finais se comuniquem, o que permite que o HOL seja evitado. Uma conexão TCP é uma via full-duplex, análoga a dois canos que enviam água, um no sentido oposto ao do outro. Esse relacionamento pode ser melhor exemplificado pela Figura 3.13. Figura 3.13: A conexão TCP Uma associação SCTP é composta de um número arbitrário de vias entre os dois pares, análogos a vários canos que enviam água, uns para um sentido, uns para o outro. Esse número arbitrário é configurado no momento da iniciação do relacionamento entre os sistemas finais. A associação SCTP pode ser melhor exemplificada pela Figura 3.14. Figura 3.14: A associação SCTP Este número arbitrário de vias caracteriza a funcionalidade dos multi-fluxos, que permite que as mensagens enviadas dentro de um mesmo fluxo vão ter sua ordem preservada. No entanto, mensagens enviadas em diferentes fluxos vão ser entregues à camada superior sem interferência da ordem de chegada das mensagens atribuı́das a outros fluxos. Por outro lado, a desordenação na entrega das mensagens é alcançada pela configuração de um flag do chunk que 3.4. A comparação entre o UDP, o TCP e o SCTP 63 indica que a ordem de entrega à camada superior deve ser feita sem qualquer restrição, independentemente do fluxo através do qual foi enviada. • Limites das mensagens: Para o TCP, os dados transportados entre dois sistemas finais através de uma conexão são considerados um fluxo5 ou seqüência de bytes. No SCTP, os limites dos dados são preservados, implementando a noção de mensagens, o que pode trazer benefı́cios para aplicações que necessitam delimitar seus dados para identificar as mensagens dentro de um fluxo de bytes. • Reconhecimento seletivo: Uma outra diferença importante entre o SCTP e o TCP é a utilização de reconhecimento seletivo, que permite que o receptor reporte a sua exata condição ao remetente sobre todos os pedaços de dados que ainda não chegaram no destino. Para o TCP, isto é uma opção, e pode estar presente em algumas implementações [23]. • Multi-homing: Uma caracterı́sticas importante do SCTP é a utilização de multi-homing. O multi-homing pode ser utilizado quando mais de um endereço IP é associado a um host. Isto geralmente está relacionado com hosts que possuem múltiplas interfaces de rede. • Encerramento: Ambos os protocolos permitem o encerramento “elegante” e “não elegante” do relacionamento. No entanto, o SCTP não permite o estado half-closed como no TCP, no qual um dos lados da conexão permanece aberto enquanto o outro se fecha. A partir do levantamento das caracterı́sticas desses protocolos, percebe-se que o SCTP provê algumas vantagens sobre o TCP, tendo em destaque o provimento de múltiplos fluxos e a entrega desordenada de mensagens dentro de uma associação, que pode ser utilizado para evitar o problema do HOL, normalmente causado pelo TCP. 5 A noção de fluxo citada não deve ser confundida com a noção de fluxos empregada pelo SCTP Capı́tulo 4 A utilização do protocolo SCTP por aplicativos de compartilhamento de arquivos em peer-to-peer As redes peer-to-peer permitem que computadores, ou nós, interconectem-se de forma distribuı́da para compartilhar arquivos. Como apresentado no Capı́tulo 2, essas redes podem utilizar diferentes arquiteturas, influenciando a maneira pela qual os nós se organizam e se relacionam, assim como as suas funções na rede. Nas arquiteturas descentralizadas, a troca de mensagens entre os nós é necessária para o funcionamento do sistema. Os aplicativos peer-to-peer utilizados para o compartilhamento de arquivos usam a troca de mensagens para a localização ou consulta dos arquivos na rede peer-topeer, e também com outros objetivos, tais como descobrir os nós nessa rede virtual. Esses aplicativos implementam três fases para o seu funcionamento: a fase de estabelecimento de conexões, a fase de consulta e a fase de transferência de arquivos. Estas fases podem sobrepor-se; por exemplo, enquanto um nó estiver efetuando a transferência de um arquivo, pode, ao mesmo tempo, estar efetuando outra consulta e, ainda, tentando estabelecer conexões com mais nós. Na fase de estabelecimento de conexões, o nó que deseja entrar na rede peerto-peer, ou nó entrante, deve encontrar os outros nós já considerados participantes. 64 Capı́tulo 4. A utilização do protocolo SCTP por aplicativos de compartilhamento de arquivos em peer-to-peer 65 Assim que um computador efetiva uma conexão com um desses nós, ele se torna, então, um nó participante da rede, podendo também efetivar outras conexões com outros nós. Na fase de consulta, o nó participante envia mensagens a outros nós, a fim de localizar arquivos na rede virtual. Esta fase de consulta consiste na propagação das mensagens através dos nós e no processo de backtrack, em que as respostas são geradas de acordo com uma métrica de similaridade entre a consulta e os arquivos compartilhados, sendo retornadas aos nós que executaram a consulta. A fase de transferência de arquivos ocorre logo que o nó solicitante recebe as respostas, que contêm não só os dados sobre arquivo, tais como tamanho e nome, mas também os dados sobre suas localizações na rede virtual. O nó solicitante, de posse desses dados, conecta-se diretamente em outro nó, para efetuar a transferência dos arquivos que procura. A Figura 4.1 mostra essas três fases. Figura 4.1: As fases de funcionamento de um aplicativo peer-to-peer para compartilhamento de arquivos Na fase de conexão, como mostrado na Figura 4.1 (a), o nó entrante é representado pelo cı́rculo com preenchimento de cor branca, e as setas pretas e pontilhadas representam o pedido de conexão que o nó entrante solicita aos nós participantes, que são representados pelos cı́rculos com preenchimento de cor cinza. As conexões já estabelecidas entre eles são representadas pelas linhas cinzas. A Figura 4.1 (b) representa a fase de consulta. Nessa fase, o nó entrante já deve ter estabelecido pelo menos uma conexão com um nó participante para poder fazer sua consulta. Dessa vez, o nó que na Figura 4.1 (a) era considerado um nó entrante, é, agora, representado como um nó participante, junto aos outros nós, pelos cı́rculos Capı́tulo 4. A utilização do protocolo SCTP por aplicativos de compartilhamento de arquivos em peer-to-peer 66 com preenchimento preto, e suas conexões pelas linhas pretas. As setas de cor cinza que partem deste nó mostram a propagação de suas consultas para os diversos nós da rede. Uma vez identificado um nó na rede que pode satisfazer à consulta, uma mensagem de resposta é enviada para o nó solicitante, fazendo uso do processo de backtrack, isto é, essa mensagem percorre, em ordem inversa, os mesmos nós usados para a localização do nó que satisfaz à consulta. Esse processo pode ser visualizado na Figura 4.1 (b) pelas setas cinzas que retornam ao nó solicitante. Na fase de transferência de arquivos, como mostra a Figura 4.1 (c), o nó, de posse de informações sobre a localização do arquivo na rede, entra em contato com o nó servente que possui o arquivo. A seta preta representa o sentido para o qual o arquivo é transferido. Nota-se que, a partir deste momento, a transferência do arquivo se dará exclusivamente a partir do nó servente ao nó requisitante, neste caso, o nó entrante. Assim sendo, essa fase ocorre “por fora” da rede overlay, pois a conexão estabelecida para a transferência do arquivo não faz parte da topologia da rede virtual, mesmo que os nós sejam vizinhos. Um aplicativo peer-to-peer, portanto, envia mensagens entre os nós da rede virtual, as quais são propagadas de acordo com um algoritmo de roteamento. Por sua vez, essas mensagens são caracterizadas por possuı́rem tamanhos variáveis e não apresentarem nenhuma restrição na ordem em que são entregues ao nó de destino. As mensagens em uma rede peer-to-peer descentralizada são comumente transferidas fazendo o uso de um protocolo orientado à conexão, pois, dessa maneira, garante-se a confiabilidade na entrega das mensagens, assim como a regulagem na inserção de dados na rede, a partir dos controles de fluxo e de congestionamento. O TCP, o protocolo comumente utilizado para a transmissão dessas mensagens, impõe uma restrição: que as mensagens sejam entregues de forma ordenada. Essa restrição não é um requisito das transferências das mensagens, e isso faz com que o desempenho dessa tarefa seja degradado pela ocorrência de atrasos indesejados na entrega dos dados à camada de aplicação. O SCTP é um outro protocolo da camada de transporte que oferece a confiabilidade e os controles de fluxo e de congestionamento, porém, a ordenação estrita é um serviço opcional. Esse protocolo pode satisfazer aos requisitos de uma aplicação 4.1. O comportamento de uma rede Gnutella 67 peer-to-peer de maneira mais eficaz que o TCP, levando a um melhor desempenho na transferência de mensagens entre os nós da rede overlay. A proposta principal deste capı́tulo é mostrar a viabilidade de usar o SCTP como protocolo da camada de transporte para essas redes overlay, sem a intenção de generalizar. Assim, algumas caracterı́sticas problemáticas referentes ao uso das redes peer-to-peer sobre o protocolo de transporte TCP, tal como o HOL, poderiam ser evitadas com o uso do SCTP, como pode ser visto neste capı́tulo. Uma análise comparativa entre os protocolos TCP e SCTP é apresentada por meio de experimentos práticos, em que duas aplicações são desenvolvidas para simular a transferência de mensagens de modo idêntico a uma rede peer-to-peer em atividade. Estes experimentos mostram o ganho do uso do SCTP sobre o TCP como protocolo da camada de transporte para redes peer-to-peer. Para executar os experimentos práticos, faz-se uso das caracterı́sticas de uma rede que implementa o protocolo Gnutella. Para tal, uma descrição do comportamento de uma rede Gnutella em atividade é apresentada, destacando as caracterı́sticas relevantes à camada de transporte. Para complementar o capı́tulo, é mostrado o desenvolvimento da aplicação Octopus, que utiliza o protocolo Gnutella sobre o protocolo SCTP. Apresentam-se os aspectos de implementação e de uso das funcionalidades do SCTP, assim como os as camadas que compõem este aplicativo e o seu funcionamento. Neste trabalho, optou-se em usar o protocolo Gnutella, versão 0.4, devido à sua simplicidade e por possuir uma documentação ampla, aberta e de fácil acesso, que permite implementar e demonstrar o ganho de desempenho do uso do SCTP, em vez do TCP, como protocolo da camada de transporte em aplicações peer-to-peer de compartilhamento de arquivos. 4.1 O comportamento de uma rede Gnutella O protocolo Gnutella utiliza a arquitetura descentralizada pura e cega e, como descrito no Capı́tulo 2, apresenta baixa escalabilidade, pois envia suas mensagens de consulta a todos os nós diretamente conectados. No entanto, possui caracterı́sticas bem definidas referentes a um protocolo de rede peer-to-peer em termos das sintaxes e das funcionalidade das mensagens, bem como da definição da arquitetura utilizada 4.1. O comportamento de uma rede Gnutella 68 e dos algoritmos de roteamento. Esta seção tem como objetivo mostrar a dinâmica da transferência de mensagens em uma rede Gnutella real para a fase de consulta. Não são consideradas, portanto, as mensagens de tipo PING, PONG e PUSH. As caracterı́sticas relevantes à camada de transporte são destacadas, tais como o volume e o tamanho das mensagens que um nó transfere em um perı́odo de tempo. Uma discussão relativa ao tempo médio da fase de consulta também se faz presente. Os valores apresentados são utilizados como referência nos experimentos descritos neste capı́tulo. 4.1.1 O tráfego gerado na fase de consulta em uma rede Gnutella Numa rede Gnutella, a largura de banda é consumida pela troca de mensagens, as quais possuem tamanhos variados, e são geradas de maneira aleatória. A fase de consulta é caracterizada por propagar um grande volume de mensagens na rede virtual. A Figura 4.2 representa essa fase em dois momentos distintos: o momento de propagação de mensagens QUERY, e o momento de propagação de mensagens QUERYHIT. Figura 4.2: A fase de consulta em uma rede Gnutella Como mostra a Figura 4.2 (a), a mensagem QUERY, representada pela letra Q, será enviada a todos os nós diretamente conectados, exceto àquele que a enviou, sendo retransmitida iterativamente até que o número máximo de encaminhamentos permitido seja atingido. A Figura 4.2 (b) mostra o processo de backtrack, em que os nós que satisfazem à 4.1. O comportamento de uma rede Gnutella 69 consulta enviam a mensagem QUERYHIT, representada pelas letras R, que percorrem o caminho de volta ao nó que originou a consulta, através do mesmo caminho que as mensagens QUERY percorreram. A estimativa de quantos bytes de mensagens que um nó transfere em média num decorrer de tempo é difı́cil de se calcular, pois as redes peer-to-peer são dinâmicas, e seu consumo de largura de banda depende de vários fatores, tais como a freqüência que os usuários efetuam consultas, as regras de descarte de pacotes, a topologia da rede virtual, entre outras variáveis. Há uma forma eficaz para efetuar essa estimativa, a partir da utilização de monitores de rede. Os monitores são aplicativos que armazenam dados estatı́sticos sobre as mensagens que atravessam um nó. Para exemplificar, e mostrar a dinâmica de uma rede Gnutella real, a seguir, é apresentado um experimento prático encontrado na literatura [22]. Esse exemplo fornece uma visão da ordem de grandeza de transmissão de QUERYs e QUERYHITs cujos valores são usados nos experimentos descritos neste capı́tulo. As estimativas foram armazenadas por três monitores em três diferentes localidades geográficas mundiais durante o tempo de uma hora [22]. O número de mensagens QUERY transferidas em intervalos de um segundo podem ser vistos na Figura 4.3. Figura 4.3: Número de QUERYs por segundo em três diferentes monitores A Figura 4.3 mostra que a quantidade de mensagens QUERY que um nó transfere a cada segundo varia aleatoriamente nos três monitores, de maneira que não há uma forma de se calcular a distribuição exata dessas mensagens num decorrer de tempo. Perı́odos de transferência mais intensos são intercalados com perı́odos menos intensos de forma não previsı́vel. A Figura 4.4 mostra a quantidade de QUERYs transferida através do monitor de número dois, em intervalos de tempo diferentes da Figura 4.3. Desta vez, são 4.1. O comportamento de uma rede Gnutella 70 utilizados intervalos de dez segundos, um minuto e cinco minutos, respectivamente. Figura 4.4: Número de QUERYs em um monitor Percebe-se pela Figura 4.4 que a quantidade de QUERYs que é transferida no decorrer do tempo varia aleatoriamente, mesmo que seja vista em intervalos de tempo maiores. A Tabela 4.1 apresenta a média final para o rastreamento de uma hora efetuado pelos monitores. Tabela 4.1: Número de QUERYs por segundo - média em uma hora Monitor QUERYs/seg (média geral) 1 45,9 2 47,9 3 52,3 Como mostra a Tabela 4.1, os três monitores apresentam uma média similar, mostrando que a localização geográfica não influencia a média de QUERYs que são transferidas. Assim que mensagens de consulta atravessam a rede e são processadas pelos nós, mensagens QUERYHIT podem ser geradas e propagadas como respostas às consultas. A quantidade de mensagens QUERYHIT registrada pelos monitores em intervalos de um segundo é mostrada na Figura 4.5. Figura 4.5: Número de QUERYHITs por segundo em três diferentes monitores Na Figura 4.5, os três monitores registram que a quantidade de QUERYHITs 4.1. O comportamento de uma rede Gnutella 71 que são transferidas é aleatória nos no decorrer do tempo. Isso ocorre, pois a quantidade dessas mensagens que são geradas depende de muitas variáveis, tais como as consultas que são efetuadas, isto é, as buscas por arquivos mais populares tendem a gerar mais respostas do que as buscas por arquivos raros. A Figura 4.6 mostra a quantidade de QUERYHITs transferida através do monitor de número dois, em intervalos de tempo diferentes da Figura 4.5. Desta vez, são utilizados intervalos de dez segundos, um minuto e cinco minutos, respectivamente. Figura 4.6: Número de QUERYHITs em um monitor Percebe-se, pela Figura 4.6, que a quantidade de QUERYHITs que é transmitida no decorrer do tempo é aleatória, mesmo que seja vista em intervalos de tempo mais longos. A Tabela 4.2 apresenta a média final para o rastreamento de uma hora efetuado pelos monitores. Tabela 4.2: Número de QUERYHITs por segundo - média em uma hora Monitor QUERYHITs/seg (média geral) 1 32,2 2 44,2 3 26,9 As médias mostradas pela Tabela 4.2 mostram que, apesar de diferentes, apresentam resultados com altas taxas de transferência de QUERYHITs, não havendo um padrão na quantidade de transferências dessas mensagens no tempo. A partir do exemplo apresentado, pode-se ter uma idéia da taxa de transferência de mensagens de consulta em uma rede Gnutella real. No entanto, a magnitude exata depende de muitas variáveis, tais como o número de usuários na rede virtual, a topologia da rede, a freqüência que os usuários efetuam consultas, a popularidade das consultas, a porcentagem de nós que compartilham arquivos, a porcentagem de nós que respondem a cada consulta, a utilização da rede em diferentes horários e 4.1. O comportamento de uma rede Gnutella 72 dias da semana, entre outras. 4.1.2 Os tamanhos das mensagens de uma rede Gnutella As mensagens geradas por um nó servente em uma rede Gnutella podem apresentar tamanhos variados. No caso da mensagem QUERY, o seu tamanho varia de acordo com a extensão da string que o usuário utiliza para efetuar a consulta. Considera-se uma consulta com a seguinte string: Iron Maiden The Fugitive. A Tabela 4.3 mostra como é a mensagem QUERY resultante. Tabela 4.3: Uma mensagem QUERY na rede Gnutella Cabeçalho Gnutella 23 bytes Velocidade Mı́nima 2 bytes String para a consulta 24 bytes NULL 1 byte Dados opcionais 0 byte Total 50 bytes A partir da string de consulta considerada, a mensagem QUERY resultante possui 50 bytes. Dos campos de uma mensagem QUERY, o único que não possui tamanho fixo é o que carrega a string de consulta do usuário e, uma vez que os tamanhos das consultas dos usuários são variáveis, não é possı́vel estimar com precisão o tamanho médio das mensagens QUERY em uma rede peer-to-peer. Para saber o tamanho médio de uma mensagem QUERYHIT, duas variáveis devem ser consideradas [35]: r, como o número médio de respostas que um nó oferece a uma dada consulta, e t, como o tamanho médio da string de cada resposta. A mensagem QUERYHIT resultante é mostrada na Tabela 4.4. Tabela 4.4: Uma mensagem QUERYHIT na rede Gnutella Cabeçalho Gnutella 23 bytes Número de hits 1 byte Porta 2 bytes Endereço IP 4 bytes Velocidade 4 bytes r *(8+t+2) bytes Conjunto de respostas Identificador de servente 16 bytes Total 50 + r *(10+t) bytes Não há uma forma precisa para estimar os valores de r e t, pois variam dinami- 4.1. O comportamento de uma rede Gnutella 73 camente dependendo da consulta efetuada. Como exemplo de cálculo do tamanho médio de uma mensagem QUERYHIT, considera-se uma rede em que os nós que podem satisfazer às consultas segundo as seguintes condições: r =1,5, e t=10 bytes. O tamanho da mensagem QUERYHIT, neste caso, é de 80 bytes. Os exemplos apresentados mostram a dinâmica de uma rede Gnutella a partir das quantidades de mensagens da fase de consulta transferidas através de três nós, representando um momento de uma rede Gnutella real. Além dessa exemplificação, uma breve discussão sobre os tamanhos médios dessas mensagens foi apresentada. Assim sendo, a partir dos dados mostrados nos exemplos, considera-se que a taxa média de envio de QUERYs em uma rede Gnutella é em torno de 40 mensagens por segundo, e a taxa média de QUERYHITs é em torno de 25 mensagens por segundo. O tamanho médio de uma QUERY é em torno de 50 bytes, e o tamanho médio da QUERYHIT é em torno de 80 bytes. Estes valores são utilizados como referência para o aplicativo, apresentado neste capı́tulo, que simula a transmissão de mensagens nó-a-nó em uma rede peer-to-peer, com o objetivo de fazer uma análise comparativa entre o TCP e o SCTP como protocolos da camada de transporte para aplicativos peer-to-peer. 4.1.3 Discussão a respeito do tempo médio da fase de consulta Diferentes parâmetros podem ser considerados para avaliar a fase de consulta [48,51]: • Número de respostas obtidas; • Precisão e revocação dos resultados obtidos; • Abrangência da consulta, calculada pelo número de nós visitados; • Tempo médio de chegada de respostas às ordens de busca; • Largura de banda consumida pela propagação de mensagens. Cada um desses parâmetros pode ser melhorado de maneiras diversas. O número de respostas obtidas pode, por exemplo, ser variado dependendo da métrica de similaridade utilizada entre os arquivos e as consultas. O número de nós visitados pode ser aumentado em se adotando uma abordagem baseada em arquitetura hı́brida, na 4.1. O comportamento de uma rede Gnutella 74 qual os chamados ultranós armazenarão os dados dos arquivos compartilhados por outros nós [39]. O parâmetro referente ao tempo médio de chegada de respostas às ordens de busca depende do desempenho na transmissão de mensagens nó-a-nó. O tempo da fase de consulta para uma única consulta pode ser medido a partir das variáveis apresentadas na Figura 4.7. Figura 4.7: Tempo de resposta a uma consulta As variáveis Ti representam o tempo de transmissão nó-a-nó de uma mensagem QUERY, onde i representa o i -ésimo salto da mensagem. As variáveis Bi representam o tempo de transmissão de uma mensagem QUERYHIT nó-a-nó de volta ao nó que originou a consulta. O tempo para uma consulta atingir um nó que pode satisfazer à consulta é calculado pela soma de todos os Ti . De maneira análoga, o tempo da resposta voltar ao nó originário é calculado pela soma de todos os Bi . O tempo da fase de consulta é dado pela soma de todos os Ti e Bi . A média aritmética da soma de todos os Ti com os Bi reflete um fator importante, que é o tempo médio que uma mensagem gasta para ser transmitida de um nó a outro. Um mecanismo para melhorar o desempenho da transmissão de mensagens nóa-nó pode trazer benefı́cios para o tempo médio da fase de consulta. As mensagens transmitidas pelos aplicativos peer-to-peer caracterizam-se por possuir tamanhos variados e por não apresentarem restrição na ordem de entrega ao nó de destino. Fazer uso do protocolo TCP para executar essa tarefa pode causar atrasos desnecessários na entrega das mensagens à camada de aplicação, degradando o tempo médio de chegada de respostas às ordens de busca. A utilização do SCTP pode melhorar o 4.2. O problema de HOL em redes peer-to-peer 75 desempenho deste parâmetro, como pode ser visto na próxima seção. 4.2 O problema de HOL em redes peer-to-peer Muitas aplicações necessitam que seus dados sejam entregues de maneira estritamente ordenada à camada de aplicação. Outras, no entanto, não necessitam desta caracterı́stica, permitindo a ordenação parcial, ou, ainda, a desordenação na entrega dos dados. Os aplicativos peer-to-peer são um exemplo que se enquadra no segundo perfil, em que a transmissão das mensagens entre os nós, principalmente aquelas relacionadas à fase de consulta, não requer que seja executada de forma ordenada. Em um ambiente peer-to-peer, cada nó possui conexões com outros; essas conexões são utilizadas para enviar e receber mensagens de maneira dinâmica. As mensagens não apresentam um relacionamento de ordenação entre si, não havendo restrições na seqüência que são entregues à camada de aplicação. A ordenação estrita na entrega das mensagens exige que a camada de transporte armazene mensagens que chegam fora de ordem, e não sejam entregues à camada de aplicação até que as mensagens de posições anteriores na seqüência sejam retransmitidas e estejam disponı́veis nos buffers do destinatário. Os aplicativos peer-to-peer requerem confiabilidade na entrega das mensagens, porém a ordem estrita na entrega das mensagens não é uma caracterı́stica necessária, ou, ainda, é um serviço que causa danos ao desempenho da transmissão de mensagens. Uma vez que a maioria dos aplicativos peer-to-peer são desenvolvidos com a utilização do TCP, obtém-se, neste caso, a confiabilidade, que é um serviço que o aplicativo demanda, e a ordem estrita na entrega das mensagens, que é um serviço que a aplicação não precisa. O protocolo SCTP oferece a confiabilidade, porém, permite que as mensagens sejam entregues de maneira não ordenada. Essa caracterı́stica pode ser utilizada para solucionar o problema do HOL e, com isso, é possı́vel melhorar o desempenho na transmissão de mensagens entre os nós. Um dos ganhos mais importantes com a melhora no desempenho da transmissão de mensagens nó-a-nó é o que se refere à fase de consulta, que passa a apresentar um tempo de resposta médio inferior àquele que utiliza o TCP na camada de transporte. Nesta seção é visto em detalhes o problema do HOL causado pelo TCP e, em 4.2. O problema de HOL em redes peer-to-peer 76 seguida, são mostradas duas soluções para esse problema com a utilização do SCTP, uma utilizando a funcionalidade dos multi-fluxos e outra, utilizando a funcionalidade da entrega desordenada de mensagens. 4.2.1 O HOL causado pelo TCP O TCP é um protocolo que apresenta ordenação estrita na entrega dos dados. As aplicações que demandam este serviço encontram no TCP as caracterı́sticas necessárias para desenvolver o seu sistema. No entanto, as aplicações que não precisam dessa ordenação estrita e são implementadas com a utilização do TCP, podem enfrentar o problema chamado HOL, ou head-of-line blocking. O HOL ocorre, no TCP, quando um segmento de dados, pertencente a uma mesma conexão, é perdido, e outros subseqüentes chegam antes do primeiro. Estes segmentos são enfileirados, e armazenados nos buffers de recebimento da camada de transporte até que o primeiro seja retransmitido e chegue ao receptor. Assim que o primeiro segmento chega, ele e os demais são entregues à camada de aplicação. O atraso na entrega de segmentos subseqüentes, causados pelo atraso de outros segmentos que faltam, é resultado da necessidade da entrega dos bytes na mesma ordem em que o remetente os enviou. A Figura 4.8 ilustra este cenário com um servidor que envia ao cliente quatro mensagens através de uma única conexão TCP. A Figura 4.8 representa a camada de aplicação e a camada de transporte, sendo possı́vel observar o comportamento da mensagem desde o momento em que é enviada da camada de aplicação à camada de transporte, assim como o momento em que a camada de transporte libera os dados à camada de aplicação. No servidor, a camada de aplicação entrega à camada de transporte os dados da mensagem de número um, representada pela sigla Msg 1. Imediatamente, a camada de transporte adiciona o seu cabeçalho à mensagem e a envia para a camada de rede1 . Os dados partem, então, para o cliente. A mensagem de número dois passa pelo mesmo processo, mas é perdida no caminho até o cliente. Isso representa, na camada de transporte, a perda de um segmento que carrega essa mensagem, ou parte dela2 . As mensagens de número três 1 A camada de rede não é mostrada pois, essa representação possui enfoque somente em como a camada de transporte manipula a liberação de dados para a camada de aplicação. 2 O TCP é orientado a bytes, mas a noção de mensagem é implementada na camada de aplicação 4.2. O problema de HOL em redes peer-to-peer 77 Figura 4.8: Transmissão de mensagens utilizando o TCP e quatro são enviadas, e chegam no cliente. No entanto, elas são enfileiradas nos buffers da camada de transporte, não sendo liberadas à camada de aplicação. O servidor detecta, então, que o segmento que carregava a mensagem de número dois, ou parte dela, não chegou ao destino. Imediatamente, o TCP faz com que esse segmento perdido seja retransmitido. Essa á uma caracterı́stica do TCP conhecida como retransmissão seletiva [20]. Assim que o destinatário recebe a mensagem de número dois por completo, ela e todas as outras que estavam aguardando sua chegada são entregues à camada de aplicação. A espera das mensagens de número três e quatro nos buffers da camada de transporte, causada pela perda da mensagem de número dois, caracteriza o HOL. As variáveis A1 e A2 representam os atrasos provocados pelo HOL. A variável A1 representa o tempo desnecessário que a mensagem de número três ficou armazenada na camada de transporte, e a variável A2 o atraso desnecessário sofrido pela mensagem de número quatro. Se a aplicação precisa da ordem estrita na entrega das mensagens, o HOL é uma delimitando o fluxo de bytes em partes, denominadas mensagens [42]. 4.2. O problema de HOL em redes peer-to-peer 78 conseqüência do serviço que esta aplicação demanda. No entanto, como no caso dos aplicativos peer-to-peer, esta ordenação não é necessária. Embora a transmissão de mensagens através de várias conexões TCP evite o HOL, do ponto de vista do uso da rede, essa opção não otimiza a utilização do canal de dados. A perda de segmento de uma conexão, como um sinal de congestionamento no caminho, não necessariamente faz com que as outras conexões diminuam sua taxa de envio de dados à rede. Assim sendo, pode ser inviável para o sistema operacional sustentar enormes quantidades de conexões TCP. 4.2.2 A utilização dos multi-fluxos O SCTP oferece o suporte a multi-fluxos. No cabeçalho do SCTP, são reservados 16 bits para designar o número do fluxo, podendo-se atingir até 65536 fluxos distintos. Este serviço permite que duas ou mais mensagens sejam enviadas através de canais distintos, os quais são independentes entre si. Isto significa que a mensagem enviada em um dos fluxos não afeta a ordem de entrega de uma outra mensagem enviada em outro fluxo. No entanto, mensagens enviadas dentro de um mesmo fluxo mantém a ordem em que foram enviadas. Com esse mecanismo, é possı́vel aliviar o HOL enviando mensagens em diferentes fluxos. As mensagens enviadas podem sofrer HOL dentro do fluxo, mas elas não afetam mensagens de outros fluxos. Essa funcionalidade é aplicável em sistemas que necessitam de ordenação em somente uma porção de seus dados, mas não quer que outras seqüências de dados afetem a primeira porção, e vice-versa. A utilização dos multi-fluxos é adequada para aplicações peer-to-peer, por exemplo, em que se pode ter diferentes canais para a transferência de arquivos. A aplicação Octopus, desenvolvida neste capı́tulo, faz uso desse mecanismo. A Figura 4.9 representa a transmissão de quatro mensagens, desta vez, com a utilização dos multi-fluxos do SCTP. Nesse exemplo, o servidor envia a primeira mensagem, através do fluxo de número um, que é perdida. A mensagem de número dois é enviada, também através do fluxo de número um. No entanto, ao atingir o destinatário, ela é enfileirada na camada de transporte, pois a mensagem de número um, que pertence ao mesmo fluxo, ainda não chegou. As mensagens de número três e quatro são enviadas através, 4.2. O problema de HOL em redes peer-to-peer 79 Figura 4.9: Transmissão de mensagens utilizando os multi-fluxos do SCTP respectivamente, dos fluxos de número dois e três, e confiavelmente entregues à camada de aplicação, sem esperar a chegada da mensagem de número um ou dois, aliviando o HOL. Como mostrado na Figura 4.9, o SCTP detecta que a mensagem de número um foi perdida, efetuando, assim, sua retransmissão. Quando essa mensagem chega ao destino, ela e a mensagem de número dois são entregues à camada de aplicação. A mensagem de número dois sofreu HOL, pois pertence ao mesmo fluxo que a mensagem de número um, e precisava ser entregue ordenadamente. A variável A representa o atraso sofrido pela mensagem de número dois. Apesar deste mecanismo ter aliviado o HOL, não o resolve de maneira completa, pois as mensagens intra-fluxo precisam ser entregues na seqüência. No entanto, se uma aplicação precisar de ordenação em somente uma parcela de fluxos de mensagens que envia, essa funcionalidade pode ser a solução. 4.2. O problema de HOL em redes peer-to-peer 4.2.3 80 A utilização da entrega desordenada de mensagens O SCTP oferece, ainda, o suporte à entrega de mensagens de forma completamente desordenada à camada de aplicação. As mensagens que forem marcadas como desordenadas, ou melhor, para serem tratadas sem considerar a ordem de chegada, independentemente do fluxo através do qual foram enviadas, são entregues à camada de aplicação assim que estiverem disponı́veis no destinatário. Isso é atingido pela marcação de um bit no chunk que carrega a mensagem. A Figura 4.10 representa a transmissão de quatro mensagens usando o SCTP, desta vez com a utilização da entrega desordenada de mensagens. Figura 4.10: Transmissão de mensagens utilizando a entrega desordenada do SCTP Neste exemplo, a mensagem de número um, marcada como desordenada, é transmitida e perdida. As mensagens de número dois, três e quatro são enviadas, também marcadas como desordenadas, e entregues à aplicação sem sofrer HOL. A mensagem de número um é, então, retransmitida, e entregue ao destinatário. Se as mensagens são marcadas como desordenadas, a identificação do fluxo é irrelevante, pois a sua entrega à camada de aplicação será imediata à sua chegada. É importante salientar que, mesmo assim, o remetente coloca o identificador de fluxo no chunk, permitindo, desta maneira, que esse valor seja utilizado para algum outro 4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer 81 fim, como, por exemplo, para classificar diferentes mensagens. No caso da aplicação Octopus, esse campo é utilizado para identificar qual o tipo de mensagem Gnutella é recebida. 4.3 Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer Para mostrar que o SCTP pode melhorar o desempenho da transmissão de mensagens, em relação ao TCP, em uma rede peer-to-peer, um ambiente de simulação é utilizado para a execução de experimentos práticos. Esta seção descreve esse ambiente e os aplicativos que implementam as suas entidades. Em seguida, são apresentadas as formas pelas quais os experimentos foram executados, bem como os resultados obtidos. O ambiente de simulação tem como objetivo implementar, de maneira similar ao comportamento de uma rede Gnutella real, a troca de mensagens entre dois nós e, assim, efetuar uma comparação entre o TCP e o SCTP como protocolos da camada de transporte para aplicações peer-to-peer de compartilhamento de arquivos. 4.3.1 Descrição do ambiente de simulação O ambiente de simulação é composto por dois nós, “Nó 1” e “Nó 2”, e um gerador de perda e atraso de pacotes. O “Nó 1” e o “Nó 2” representam nós quaisquer, em uma rede peer-to-peer, que possuem uma conexão, ou associação, estabelecida. O gerador de perda e de atraso de pacotes emula o comportamento que a Internet provoca sobre as mensagens que trafegam do “Nó 1” ao “Nó 2”, e do “Nó 2” ao “Nó 1”. Nesta simulação, o “Nó 1”, o “Nó 2” e o gerador de perda e atraso de pacotes são aplicações que são executadas em computadores. O aplicativo utilizado para atuar como gerador de perda e atraso de pacotes é o NIST Net [3]. O NIST Net é uma ferramenta para o sistema operacional Linux que facilita a execução de testes e experimentos, emulando uma rede hipotética em um computador, permitindo a definição de parâmetros tais como atraso, jitter, limitação de largura de banda, perda e duplicação de pacotes, entre outros, semelhante ao que acontece em uma rede de computadores real. 4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer 82 Figura 4.11: A emulação do comportamento esperado de uma rede de computadores por meio do NIST Net Como mostrado na Figura 4.11, uma rede hipotética, representada pela Internet, sobre a qual o computador A se comunica com o computador C, e o computador B se comunica com o computador D, tem seu comportamento emulado em laboratório. Essa emulação é atingida pela configuração de valores esperados de comportamento desta rede hipotética como parâmetros do aplicativo NIST Net. A utilização deste aplicativo é bem simples, pois toda a configuração pode ser feita por meio de uma interface gráfica, como mostrado no Anexo A. O computador utilizado para executar o aplicativo que atua como o “Nó 1” possui um processador Intel Celeron de 2.0 Ghz com 256 Mbytes de memória RAM, rodando o sistema operacional Linux com kernel de versão 2.6.10, da distribuição Slackware, versão 10.1. O computador utilizado para executar o aplicativo que atua como o “Nó 2” possui um processador Intel Celeron de 1.2 Ghz com 128 Mbytes de memória RAM, rodando o sistema operacional Linux com kernel de versão 2.6.9 da distribuição Slackware, versão 9.0. O computador utilizado para executar o aplicativo NIST Net, atuando como gerador de perda e atraso de pacotes possui um processador Pentium III de 866 Mhz com 128 Mbytes de memória RAM, rodando o sistema operacional Linux com 4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer 83 kernel de versão 2.6.9 da distribuição Slackware, versão 9.0. Os aplicativos que implementam as entidades do ambiente de simulação fazem uso da arquitetura de protocolos TCP/IP. O aplicativo NIST Net atua na camada de rede, e os aplicativo que implementam o “Nó 1” e o “Nó 2” atuam na camada de aplicação em dois momentos distintos: primeiramente utilizando o protocolo TCP na camada de transporte, e depois utilizando o protocolo SCTP. A Figura 4.12 mostra a pilha de protocolos utilizada por cada entidade, assim como o relacionamento entre as diversas camadas. Figura 4.12: As pilhas de protocolos utilizadas pelos aplicativos do ambiente de simulação A Figura 4.12 (a) representa a pilha de protocolos com a utilização do TCP. A conexão TCP é representada como a comunicação lógica entre dois processos rodando em dois computadores diferentes, e as setas, uma em cada sentido, representam a natureza full-duplex da conexão. O relacionamento entre a camada de aplicação, implementando um aplicativo peer-to-peer, e a camada de transporte, implementando o TCP, é representada por uma única seta, pois há uma única via, full-duplex, através da qual o processo se comunica com a camada de transporte. 4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer 84 Na Figura 4.12 (b), o destaque é dado pelo uso do SCTP na camada de transporte. A associação SCTP possui várias setas em cada sentido pois permite que diversos fluxos sejam utilizados para a comunicação entre processos em diferentes computadores, através de uma única associação. As setas indicando o relacionamento entre a camada de aplicação, implementando um aplicativo peer-to-peer, e a camada de transporte, implementando o SCTP, representam as múltiplas vias pelas quais o aplicativo peer-to-peer pode receber e enviar suas mensagens utilizando os multi-fluxos. A aplicação que implementa o “Nó 1”, ou aplicação geradora e receptora de pacotes, cria duas threads, sendo que uma delas gera e transmite mensagens, e outra recebe mensagens. A aplicação que implementa o “Nó 2” atua simplesmente como echo server. O código fonte destas aplicações pode ser encontrado na ı́ntegra no Apêndice A. Para essa implementação, fez-se uso da linguagem C, com utilização das bibliotecas pthread lib, e da libsctp [12, 13, 40]. O “Nó 1” atua como gerador e receptor de pacotes, simulando o tráfego de mensagens dentro de uma rede overlay. Os pacotes gerados por esse nó são enviados ao “Nó 2”. O “Nó 2” atua como um echo server, enviando as mensagens de volta ao “Nó 1”, assim que estiverem disponı́veis. Desta maneira, é possı́vel efetuar cálculos de desempenho individualmente para cada mensagem, em que o trânsito de uma mensagem do “Nó 1” ao “Nó 2”, e do “Nó 2” ao “Nó 1” representam dois hops da mensagem na rede overlay. O funcionamento destas aplicações é mostrado nos diagramas da Figura 4.13. A Figura 4.13 (a) mostra o funcionamento do aplicativo gerador e receptor de pacotes, ou “Nó 1”, e a Figura 4.13 (b) mostra o funcionamento do aplicativo que atua como echo server, ou “Nó 2”. O aplicativo que atua como echo server, mostrado na Figura 4.13 (b), simplesmente recebe o pacote e o devolve ao nó que o enviou. Caso esse pacote seja enviado fazendo o uso do protocolo SCTP, o echo server devolve esse pacote através do mesmo fluxo que o recebeu. O aplicativo gerador e receptor de pacotes cria duas threads: uma que gera e envia os pacotes, e outra que recebe os pacotes que foram devolvidos pelo echo server. A primeira thread gera e envia os pacotes de acordo com parâmetros definidos pelo 4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer 85 Figura 4.13: As fases de funcionamento dos aplicativos gerador e receptor de pacotes e echo server usuário. Um desses parâmetros é o protocolo da camada de transporte, escolhido entre o TCP e o SCTP. Os parâmetros comuns a ambos os protocolos são: • Endereço local e porta local: Um único endereço e uma única porta local devem ser especificados para iniciar o aplicativo gerador e receptor de pacotes; • Endereço remoto e porta remota: Especifica o endereço e a porta do echo server ; • Número de mensagens a enviar: Para cada experimento, várias de mensagens são encaminhadas ao echo server, e essa quantidade de mensagens é definida pelo usuário por meio deste parâmetro; • Intervalo de tempo entre as mensagens: Esse parâmetro, medido em milisegundos, representa o espaço de tempo entre cada nova mensagem gerada e enviada ao echo server. Por meio dele, é possı́vel definir quantas mensagens o aplicativo vai enviar em um espaço de tempo. Pretende-se, com esse parâmetro, simular maiores e menores taxas de tráfego de mensagens entre os nós; • Tamanho dos pacotes: As mensagens em uma rede peer-to-peer podem apresentar diferentes tamanhos. Esse parâmetro permite definir o tamanho, em bytes, das mensagens que são encaminhada ao outro nó; 4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer 86 Quando o SCTP é selecionado como protocolo da camada de transporte, outros parâmetros podem ser configurados, os quais fazem uso de funcionalidades não presentes no TCP: • Número de fluxos: Esse parâmetro define a quantidade de fluxos que o aplicativo deve utilizar para enviar as mensagens. Cada uma delas vai ser enviada a um fluxo diferente, segundo a polı́tica round-robin; • Envio desordenado de mensagens: Se este parâmetro for habilitado, as mensagens serão enviadas de forma desordenada, independentemente do número de fluxos utilizado. Cada thread do aplicativo gerador e receptor de pacotes possui etapas de funcionamento distintas. A Figura 4.14 mostra o funcionamento da thread geradora de pacotes, e a Figura 4.15 mostra o funcionamento da thread de recebimento de pacotes. Figura 4.14: O funcionamento da thread geradora de pacotes Quando uma mensagem é gerada, um “tag de identificação” é definido de forma que, no recebimento desta mensagem, seja possı́vel interromper o seu temporizador especı́fico. O “manipulador de fluxos e ordenação” é utilizado pelo protocolo SCTP a partir dos parâmetros “número de fluxos” e “envio desordenado de mensagens”. É nesta entidade que é definido através de qual fluxo uma mensagem é enviada e, também, se a mensagem vai ser enviada de forma desordenada. O “gerador de atraso entre mensagens” suspende a execução da thread entre o envio de cada mensagem por um tempo definido pelo usuário. O objetivo desse atraso, é simular um número determinado de mensagens que são enviadas num espaço de 4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer 87 tempo. Assim que a mensagem é liberada para ser enviada, um temporizador, associado ao seu identificador, é iniciado. A mensagem é, então, enviada ao echo server, que, ao receber a mensagem, a envia de volta ao “Nó 1”. Quando o SCTP é utilizado como protocolo da camada de transporte, as mensagens enviadas são devolvidas pelo echo server através do mesmo fluxo que as receberam, e se forem enviadas de forma desordenada, devem ser devolvidas da mesma maneira. Figura 4.15: O funcionamento da thread receptora de pacotes A thread receptora de pacotes, com suas etapas mostradas na Figura 4.15, confere o tag de identificação da mensagem e interrompe o temporizador ao qual está associada. Com isso, é possı́vel obter a latência de de envio e recebimento para cada mensagem, como exemplificado nas Figuras 4.16, 4.17 e 4.18. Figura 4.16: A cálculo da latência de uma mensagem - Exemplo 1 Na Figura 4.16 a mensagem é enviada do “Nó 1”, que gera e recebe os pacotes, para o “Nó 2”, que envia as mensagens de volta ao primeiro nó. A variável L1 representa a latência da mensagem de número um, representada por Msg 1. Observase pela Figura 4.16, que o tempo de latência é calculado tomando como base o 4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer 88 momento em que a camada de aplicação envia a mensagem, e o momento em que essa mesma camada a recebe, ou seja, o temporizador só é interrompido quando a camada de aplicação recebe a mensagem. Figura 4.17: O cálculo da latência de uma mensagem - Exemplo 2 A Figura 4.17 mostra o comportamento de uma mensagem perdida no caminho de ida, do “Nó 1” ao “Nó 2”, e no caminho de volta, do “Nó 2” ao “Nó 1”. A latência final, representada pela variável L1, é computada a partir do momento em que a camada de aplicação envia os dados para a camada de transporte, e não recomeça a contagem caso haja uma retransmissão. A Figura 4.18 mostra o envio de duas mensagens com o uso do TCP, em que a primeira é perdida no caminho de ida. Isso faz com que a mensagem de número dois sofra o HOL e fique armazenada na camada de transporte até que a primeira mensagem seja retransmitida e chegue ao “Nó 2”. Assim que isso acontece, as duas mensagens são enviadas de volta ao “Nó 1”. As variáveis L1 e L2 representam, respectivamente, a latência final da mensagem de número um e da mensagem de número dois. Através deste ambiente, pretende-se simular uma rede peer-to-peer nó-a-nó, comparando os protocolos TCP e SCTP como protocolos da camada de transporte para aplicativos peer-to-peer. Esta comparação é atingida por meio da reprodução das caracterı́sticas de transferência de mensagens em uma rede peer-to-peer, no ambi- 4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer 89 Figura 4.18: O cálculo da latência de uma mensagem - Exemplo 3 ente em que o comportamento e a dinâmica das mensagens são obtidas através dos parâmetros passados pelo usuário e, também, pela configuração do comportamento que a Internet provoca nas mensagens transmitidas. A métrica de desempenho é a média das latências de um dado número de mensagens. 4.3.2 A simulação da transferência de mensagens numa rede peer-topeer Para efetuar os experimentos que simulam a transferência de mensagens numa rede peer-to-peer, vários parâmetros devem ser configurados nos aplicativos que compõem o ambiente de simulação, não só naqueles que implementam o “Nó 1” e “Nó 2”, mas também naquele que atua como o gerador de atraso e perda de pacotes. Os parâmetros configurados neste último aplicativo são: • RTT: Os valores de RTT (Round Trip Time) entre os nós de uma rede peer-topeer são variados. Uma vez que eles podem se situar em diferentes localidades geográficas, um valor médio de escala mundial para este parâmetro é difı́cil de ser estimado. Para efetuar os experimentos aqui descritos, é utilizado o RTT médio registrado entre dois computadores, um localizado no Brasil e outro nos Estados Unidos, no mês de janeiro de 2006, que é de aproximadamente 200 milisegundos [4]; 4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer 90 • Taxa de perda: Os valores de taxas de perda entre dois diferentes nós em uma rede peer-to-peer são variáveis, pois os pacotes que trafegam de um nó a outro podem atravessar caminhos que apresentam baixas ou altas taxas. Assim sendo, os experimentos mostram resultados para taxas de perda de 0% a 20%, permitindo, assim, uma análise geral do comportamento da transmissão de mensagens sob diferentes condições, referentes à perda de pacotes. Os parâmetros configurados nos aplicativos que implementam o “Nó 1” e “Nó 2” são: • Intervalo de tempo entre mensagens: Os dados descritos na Seção 4.1.1 são utilizados como referência para a configuração deste parâmetro. O valor médio de transmissão de QUERYs em uma rede Gnutella é de, aproximadamente, 40 mensagens por segundo e de QUERYHITs é de, aproximadamente, 25 mensagens por segundo; • Tamanho das mensagens: Os dados descritos na Seção 4.1.2 são utilizados como referência para a configuração deste parâmetro. O tamanho médio das mensagens QUERY é de 50 bytes e das mensagens QUERYHIT é de 80 bytes; • Número de mensagens: Em cada experimento são transmitidas 100 mensagens. Desta forma, é possı́vel obter a latência média da transmissão de mensagens, calculada pela média da soma das latências de cada uma delas. Os experimentos são executados em duas etapas; a primeira é executada fazendo uso dos parâmetros mostrados na Tabela 4.5. Tabela 4.5: Parâmetros dos experimentos da primeira etapa P arâmetro Valor Quantidade de mensagens 100 Tamanho das mensagens 80 bytes Quantidade de mensagens por segundo 25 Na primeira etapa, cada experimento consiste no envio de 100 mensagens de 80 bytes, com um intervalo de 40 milisegundos entre cada uma delas, enviando, desta forma, 25 mensagens por segundo. Cada mensagem é enviada ao echo server, que responde imediatamente. São armazenadas as latências de cada uma das 100 4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer 91 mensagens, e o resultado final é a média aritmética das latências de todas elas, que representa o tempo médio que uma mensagem leva para ir e voltar do echo server. São executados dez experimentos para cada valor de taxa de perda configurada, que variam de 0% a 20%. Foram escolhidos dez experimentos pois, desta forma, consegue-se mostrar a dinâmica da variação de desempenho entre as amostras sob uma mesma taxa de perda. Em cada um dos experimentos é calculado o tempo médio de envio de mensagens com a utilização do protocolo TCP, com a utilização do protocolo SCTP enviando mensagens de forma desordenada, e com a utilização dos multi-fluxos do SCTP, sendo que três fluxos são utilizados. A utilização de três fluxos é meramente ilustrativa, para mostrar que o HOL pode ocorrer dentro de um mesmo fluxo. A segunda etapa é executada com os parâmetros apresentados na Tabela 4.6. Tabela 4.6: Parâmetros dos experimentos da segunda etapa P arâmetro Valor Quantidade de mensagens 100 Tamanho das mensagens 50 bytes Quantidade de mensagens por segundo 40 Na segunda etapa, cada experimento consiste no envio de 100 mensagens de 50 bytes, com um intervalo de 25 milisegundos entre cada uma delas, ou seja, são enviadas 40 mensagens por segundo. Da mesma forma que na primeira etapa, as taxas de perda variam de 0% a 20%, e para cada experimento são utilizados o TCP, o SCTP com envio de mensagens de forma desordenada, e o SCTP com a utilização de três fluxos. 4.3.3 O desempenho dos protocolos TCP e SCTP Esta seção mostra os resultados comparativos entre o SCTP e o TCP. Os dez experimentos que são executados para cada taxa de perda configurada são indicados, nas figuras, pelas letras de A até J. As legendas das barras significam, no caso de “TCP”, a utilização do TCP na camada de transporte, no caso de “SCTP-D”, a utilização do SCTP com entrega de mensagens de forma desordenada e, no caso de “SCTP-F3”, a utilização dos multi-fluxos do SCTP com três fluxos. São mostrados os resultados para algumas taxas de perda e, também, um gráfico geral que consta 4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer 92 a média dos resultados de todos os experimentos, para a duas etapas. As Figuras 4.19, 4.20 e 4.21 mostram os resultados de 30 experimentos da primeira etapa, sendo dez experimentos com a taxa de perda configurada em 0%, dez com a taxa de perda configurada em 10% e dez com a taxa de perda configurada em 20%. Figura 4.19: Resultados da primeira etapa com taxa de perda de 0% Figura 4.20: Resultados da primeira etapa com taxa de perda de 10% 4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer 93 Figura 4.21: Resultados da primeira etapa com taxa de perda de 20% Observa-se, pelos resultados dos experimentos, que o desempenho na transmissão de mensagens com a utilização do TCP é degradada, ou seja, o tempo médio da transmissão de mensagens nó-a-nó aumenta, quando se aumenta a taxa de perda. Observa-se, ainda, que o desempenho do SCTP com a utilização dos três fluxos é um pouco inferior ao SCTP com a utilização da entrega desordenada de mensagens pois, mesmo aliviando o HOL em relação ao TCP, ainda há casos onde ocorre o HOL intra-fluxo. Quando a taxa de perda é 0% o SCTP não demonstra ter ganhos significativos em relação ao TCP, no entanto, a diferença na latência média de aproximadamente 0,7 segundos deve-se às diferenças de implementação dos protocolos, assim como às funções adicinais da camada de aplicação que o TCP utiliza para atingir a confiabilidade completa. Os tempos médios da transmissão de 100 mensagens em cada um dos experimentos, de acordo com o comportamento configurado nesta primeira etapa, mostram que o SCTP se adapta melhor ao comportamento de uma rede peer-to-peer que o TCP pois, alivia o HOL com a utilização dos multi-fluxos e evita completamente o HOL com a utilização da entrega desordenada de mensagens. A Figura 4.22 mostra os resultados médios dos experimentos para taxas de perda de 0% a 20%. Cada ponto no gráfico representa a latência média do envio de 1000 mensagens. 4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer 94 Figura 4.22: Média dos resultados da primeira etapa - variação da taxa de perda de 0% a 20% Observa-se pelo gráfico geral que quanto maior a taxa de perda, mais degradado é o desempenho do TCP em relação ao SCTP. Isso ocorre por causa do problema do HOL, que faz com que as mensagens que chegam fora de ordem fiquem aguardando na camada de transporte e não sejam liberadas à camada de aplicação. As Figuras 4.23, 4.24 e 4.25 mostram os resultados de 30 experimentos, desta vez com os parâmetros da segunda etapa, sendo dez experimentos com a taxa de perda configurada em 0%, dez com a taxa de perda configurada em 10% e dez com a taxa de perda configurada em 20%. Figura 4.23: Resultados da segunda etapa com taxa de perda de 0% 4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer Figura 4.24: Resultados da segunda etapa com taxa de perda de 10% Figura 4.25: Resultados da segunda etapa com taxa de perda de 20% 95 4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer 96 Os resultados dos experimentos feitos nesta etapa não demonstraram ser significantemente diferentes daqueles executados na primeira etapa. Mesmo com o envio de maior quantidade de mensagens por segundo e, ainda, de mensagens de menor comprimento, o comportamento causado pelo HOL foi similar. A Figura 4.26 mostra os resultados médios de todos os experimentos da segunda etapa. Figura 4.26: Média de resultados da segunda etapa - variação da taxa de perda de 0% a 20% Observa-se na Figura 4.26, como na primeira etapa, que altas taxas de perda de pacotes tendem a degradar o desempenho da transmissão de mensagens nó-anó quando o protocolo TCP é utilizado na camada de transporte. Assim sendo, a utilização do SCTP pode ser mais adequada para esta tarefa. Ainda, como discutido na Seção 4.1.3, caso seja melhorado o tempo de transmissão de mensagens nó-a-nó, o tempo médio de resposta às consultas em uma rede peer-to-peer pode ser melhorado. Foram mostradas duas utilizações para o SCTP, uma com os multi-fluxos, em que foram usados três fluxos, e outra com a entrega desordenada de mensagens. A forma de utilização destas funcionalidades em um aplicativo peer-to-peer depende da especificação do protocolo e dos serviços demandados pelo aplicativo. O aplicativo Octopus, descrito na próxima seção, faz uso dessas funcionalidades, mais especificamente a entrega desordenada de mensagens para o transporte de mensagens Gnutella, e a entrega ordenada de mensagens para a transferência de arquivos entre dois nós, ambas através de uma única associação. 4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus 4.4 97 A aplicação peer-to-peer de compartilhamento de arquivos Octopus Esta seção descreve o desenvolvimento do aplicativo Octopus. Este aplicativo permite que os usuários consultem os nós em uma rede overlay em busca de arquivos. Uma vez encontrado um arquivo, o usuário, de posse de sua localização, pode efetuar a sua transferência. O Octopus utiliza os conceitos do SCTP e do Gnutella, versão 0.4, descritos nos capı́tulos anteriores, em uma rede peer-to-peer cuja camada de transporte é implementada por meio do SCTP. Esta seção tem como objetivo descrever um protótipo de aplicativo que mostra como algumas das funcionalidades do SCTP podem ser aproveitadas por um aplicativo peer-to-peer de compartilhamento de arquivos, sem a intenção de abranger completamente todas as formas de uso do SCTP para este, ou outros gêneros e arquiteturas de sistemas peer-to-peer. O Octopus foi desenvolvido fazendo uso da linguagem C com as bibliotecas pthread lib, libsctp e a GTK lib, versão 2.0, para a interface gráfica, e seu código fonte pode ser encontrado na ı́ntegra no Apêndice B. 4.4.1 O protocolo Gnutella com o uso do SCTP O protocolo da camada de aplicação utilizado pelo aplicativo Octopus é o Gnutella, versão 0.4. No entanto, algumas modificações no formato do cabeçalho das mensagens Gnutella podem ser feitas de modo a melhor utilizar as funcionalidades oferecidas pelo protocolo da camada de transporte SCTP. Dois campos são removidos do cabeçalho das mensagens do protocolo Gnutella: o campo de tamanho do payload e o de descritor de payload. A Figura 4.27 mostra a reestruturação do cabeçalho. Figura 4.27: Novo cabeçalho para o protocolo Gnutella A Figura 4.27 (a) mostra o cabeçalho antigo, e a Figura 4.27 (b) mostra o 4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus 98 cabeçalho reestruturado: • O campo que indica o tamanho do payload é removido pois o SCTP é orientado a mensagens, não havendo a necessidade de se delimitar o fluxo de bytes com campos adicionais; • O campo de descritor de payload é removido pois, para detectar qual tipo de mensagem está sendo entregue à camada de aplicação, utiliza-se o identificador do fluxo que a mensagem foi enviada. 4.4.2 O funcionamento do aplicativo Octopus O aplicativo Octopus foi desenvolvido levando em consideração as três fases, descritas na introdução deste capı́tulo, referentes à conexão, à consulta e à transferência de arquivos em redes peer-to-peer. Como mostra o fluxograma da Figura 4.28, os estágios tı́picos3 de funcionamento do aplicativo Octopus são: • Abrir servidor: Neste estágio, o aplicativo cria uma thread que inicia o servidor de associações. Por meio deste servidor, o Octopus pode aceitar pedidos de estabelecimento de associação de outros nós; • Estabelecer associações: Neste estágio, o aplicativo solicita o estabelecimento de associação a nós cujo endereço IP o usuário forneceu, ou que o aplicativo buscou em uma base de endereços própria; • Fazer consulta: O usuário pode efetuar uma consulta que vai ser propagada através da rede caso o aplicativo tenha estabelecido, ao menos, uma associação; • Receber resposta: Caso o aplicativo receba uma mensagem de resposta a uma consulta feita pelo usuário, os dados desta resposta são mostrados na tela; • Ativar transferência de arquivos: Se o usuário deseja obter um dos arquivos cujos dados foram apresentados na tela, este estágio é, então, ativado. 3 Usa-se o termo “tı́pico”, pois esses estágios podem ser intercalados e sobrepostos, diferindo da maneira pela qual é apresentada no fluxograma. Por exemplo, ao mesmo tempo que uma consulta está em andamento, o usuário pode decidir se associar a um outro nó e, ainda, pode já estar efetuando a transferência de um arquivo. 4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus Figura 4.28: O fluxograma de funcionamento do Octopus 99 4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus 100 O modo com que as mensagens Gnutella e os arquivos são transmitidos no Octopus é diferente da forma pela qual essas tarefas são normalmente executadas por um aplicativo que faz uso do TCP na camada de transporte. O SCTP pode utilizar o mesmo canal de relacionamento para executar ambas as tarefas simultaneamente. As Figuras 4.29 e 4.30 mostram esses dois casos. Figura 4.29: Um aplicativo peer-to-peer utilizando o TCP na camada de transporte Um aplicativo que utiliza o TCP na camada de transporte normalmente utiliza uma conexão dedicada para a transmissão das mensagens Gnutella e, para cada arquivo que deseja transmitir, uma nova conexão é estabelecida, como mostrado na Figura 4.29. Por sua vez, o aplicativo Octopus faz uso de uma única associação SCTP para a transferência das mensagens de negociação da “conexão” Gnutella, das mensagens PING, PONG, QUERY e QUERYHIT4 , e que pode, ainda, ser utilizada para a negociação e transferência de mais de um arquivo simultaneamente5 , como mostra a Figura 4.30. 4 Nesta versão do Octopus não foi implementada a mensagem PUSH. Nesta versão do Octopus o número máximo de arquivos a ser transferidos simultaneamente de um mesmo nó é de três arquivos. 5 4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus 101 Figura 4.30: O aplicativo Octopus utilizando o SCTP na camada de transporte Há dois cenários a serem considerados a respeito da utilização da associação SCTP para a transferência de arquivos: aquele em que o arquivo a ser transferido está em um nó que já é vizinho do nó requerente, ou seja, está localizado a um hop de distância, e aquele em que o arquivo a ser transferido está localizado em um nó que não é vizinho do nó requerente, ou seja, está localizado a mais de um hop de distância. No caso de o arquivo estar localizado em um nó vizinho, a associação já estabelecida é utilizada para a negociação e transferência deste arquivo, sem a necessidade de se estabelecer outra associação entre esses nós. Há a possibilidade, ainda, de se transferir mais de um arquivo simultaneamente, junto com as mensagens Gnutella, através de uma única associação. Por outro lado, caso o arquivo esteja localizado em um nó que está a mais de um hop de distância do nó requerente, uma associação deve ser estabelecida entre eles. A nova associação pode ser utilizada para transferir mais de um arquivo simultaneamente e, também, para transmitir mensagens Gnutella. 4.4.3 A arquitetura do aplicativo Octopus O aplicativo Octopus é composto basicamente de quatro camadas: a camada de comunicação, a camada de tratamento de mensagens, a camada de armazenamento e a camada de interface. A camada de interface é aquela que implementa a interface 4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus 102 gráfica que o usuário pode utilizar para executar as funções do aplicativo. Os módulos que compõem a camada de armazenamento auxiliam o funcionamento do aplicativo por meio da implementação do cache de associações, do host cache, do cache de identificadores de mensagens e do repositório de arquivos compartilhados. A camada de tratamento de mensagens implementa o manipulador de mensagens, que é a entidade que faz o reconhecimento das mensagens Gnutella de acordo com o fluxo através do qual foram enviadas, além de providenciar os seus tratamentos. Ainda, esta entidade trata a negociação e a transferência de arquivos, tanto como cliente, quanto como servidor, ou seja, o próprio manipulador de mensagens faz a negociação e o envio de arquivos que são requisitados por outros nós, ou que são transferidos de outros nós para o nó local. A camada de comunicação é aquela que implementa os protocolos SCTP e IP, responsáveis, respectivamente pelas camadas de transporte e camada de rede. A Figura 4.31 mostra as camadas do aplicativo e os relacionamentos dos diferentes módulos, destacando, na camada de interface, os eventos que podem ocorrer durante o tempo de execução. Na camada de armazenamento, o módulo de cache de associações armazena informações sobre as associações que o nó local estabeleceu com outros nós, tanto aquelas que o próprio usuário solicitou, quanto aquelas que outros nós estabeleceram com o nó local. Este módulo é sempre atualizado quando uma nova associação é estabelecidada, ou quando há um encerramento em alguma associação do cache. A estrutura que armazena as informações do cache de associações é implementada através de uma estrutura cujo código é mostrado a seguir: struct cacheAssociacoes{ int numeroSocket; struct sockaddr_in endereco; }; São armazenados o número do descritor do socket para o acesso a um determinado nó, assim como as informações do endereço, em uma estrutura do tipo sockaddr in. É possı́vel, por meio de uma chamada da interface gráfica, imprimir os dados contidos 4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus Figura 4.31: A arquitetura do aplicativo Octopus 103 4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus 104 no cache de associações no shell no Linux, como mostrado na Figura 4.32. Figura 4.32: A impressão do cache de associações no shell do Linux O módulo de host cache armazena endereços de nós em que o usuário obteve sucesso no estabelecimento da associação, e também endereços que foram recebidos por meio das mensagens PONG, em resposta ao PING requerido pelo usuário. O host cache é utilizado para facilitar o estabelecimento de associações, pois armazena endereços de possı́veis nós participantes da rede peer-to-peer em um arquivo, que pode ser utilizado em outras sessões do aplicativo. É possı́vel, por meio de uma chamada da interface gráfica, imprimir os dados contidos no host cache no shell no Linux, como mostrado na Figura 4.33. Figura 4.33: A impressão do host cache no shell do Linux O cache de identificadores de mensagens é utilizado para três propósitos: a detecção de mensagens que sofreram loops dentro da rede, a execução do backtrack de mensagens de resposta e a detecção de mensagens de resposta que pertencem 4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus 105 ao nó local, providenciando, assim, a exibição das informações na interface gráfica. A seguinte estrutura é utilizada para implementar o cache de identificadores de mensagens: struct cacheIDMensagens { int ID; int NumeroSocket; struct sockaddr_in endereco; }; O repositório de arquivos compartilhados contém os arquivos que o usuário deseja disponibilizar para os nós da rede overlay. Para adicionar arquivos neste repositório, o usuário deve copiá-los no diretório “ArquivosCompartilhados”, tendo como referência o diretório que contém o executável do aplicativo Octopus. O “ı́ndice de arquivos” não apresenta nenhuma estrutura especı́fica de armazenamento, representando somente uma chamada da aplicação que faz referência aos nomes dos arquivos. O manipulador de mensagens é a entidade que faz o tratamento das mensagens Gnutella, e que implementa o cliente e o servidor de arquivos. Para detectar qual tipo de mensagem o nó recebeu e encaminhá-la para a função que faz o seu tratamento correto, assim como para efetuar a transferência de arquivos simultaneamente através de uma única associação, este módulo faz uso da funcionalidade dos multi-fluxos do SCTP, como mostrado na Figura 4.34. Os fluxos numerados de um a quatro são utilizados para o envio das mensagens Gnutella 6 . O fluxo de número cinco é utilizado para a negociação da transferência de um arquivo. Todos estes fluxos utilizam o envio de mensagens de forma desordenada, pois não importa a seqüência em que são entregues à camada de aplicação. As mensagens que compõem um arquivo são entregues a partir dos fluxos seis, sete ou oito. Para cada mensagem que chega no servente, o conteúdo útil é armazenado em um 6 O fluxo de número zero é utilizado para as mensagens de handshake do Gnutella, mas não foi ilustrado nesta figura. 4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus 106 Figura 4.34: A utilização dos multi-fluxos pelo aplicativo Octopus buffer. Em seguida, identifica-se através de qual fluxo a mensagem chegou e, então, o conteúdo do buffer é passado para a função que trata a mensagem apropriadamente. As mensagens Gnutella são identificadas da seguinte forma: • PINGs: Devem ser enviadas através do fluxo 1. Ao receber uma mensagem, se o manipulador de mensagens detecta que chegou através do fluxo 1, ele a encaminha para a função que trata PINGs. • PONGs: Devem ser enviadas através do fluxo 2. Ao receber uma mensagem, se o manipulador de mensagens detecta que chegou através do fluxo 2, ele a encaminha para a função que trata PONGs. • QUERY: Devem ser enviadas através do fluxo 3. Ao receber uma mensagem, se o manipulador de mensagens detecta que chegou através do fluxo 3, ele a encaminha para a função que trata QUERYs. • QUERYHIT: Devem ser enviadas através do fluxo 4. Ao receber uma mensagem, se o manipulador de mensagens detecta que chegou através do fluxo 4, ele a encaminha para a função que trata QUERYHITs. 4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus 107 Para identificar o número do fluxo através do qual a mensagem chegou, a API (Application Programming Interface) do SCTP preenche uma estrutura que contém as informações sobre o chunk que carregou essa mensagem, e confere o campo sinfo stream da estrutura a seguir: struct sctp_sndrcvinfo{ uint16_t sinfo_stream; uint16_t sinfo_ssn; uint16_t sinfo_flags; uint32_t sinfo_ppid; uint32_t sinfo_context; uint8_t sinfo_dscp; sctp_assoc_t sinfo_assoc_id; } A seguinte função é utilizada para receber as mensagens através do socket: struct sctp_sndrcvinfo sri; quantidadeRecebida=sctp_recvmsg(sock_fd,readbuf,200, struct sockaddr *) &cliaddr, &len,&sri,&msg_flags); O campo sinfo stream da estrutura sctp sndrcvinfo, declarada no programa fonte como a variável sri, é utilizado para identificar em qual fluxo a mensagem chegou, e encaminhá-la para a função que faz o seu tratamento. Os eventos que podem ocorrer neste aplicativo, os quais interagem com a interface gráfica, são: o usuário fazer o pedido de estabelecimento de associação, o usuário efetuar uma consulta, o usuário requerer a transferência de um arquivo, e o aplicativo receber uma mensagem de resposta a uma consulta do usuário. A interface gráfica do Octopus é mostrada na Figura 4.35: • Associar: Ao pressionar este botão, o aplicativo tenta se associar ao nó cuja localização é dada pelas informações fornecidas nos campos “Endereço” e “Porta”. Caso haja sucesso na associação, o endereço em questão é adicionado ao host cache; 4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus 108 Figura 4.35: A interface gráfica do aplicativo Octopus • Consultar: Caso uma associação tenha sido efetivada, o usuário pode consultar os nós da rede por arquivos, preenchendo o campo do texto e, em seguida, pressionando o botão “Consulta”. O identificador da mensagem QUERY gerada é armazenado no cache de identificadores de mensagem. Desta forma, se uma mensagem QUERYHIT for detectada, é possı́vel identificar se a resposta pertence ao nó local; • Download : Caso alguma resposta tenha chegado, as informações a respeito dos arquivos são adicionadas à lista de respostas. O usuário que decide fazer a transferência de algum arquivo deve indicar na lista a opção desejada e pressionar “Download ”; • Associações: Imprime no shell do Linux as informações sobre as associações que estão ativas; • Ping: Envia uma mensagem PING a todos os nós diretamente associados, 4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus 109 como forma de atualizar o host cache local. Os nós respondem ao PING com um PONG para cada nó que tenham em seu host cache; • Associa Host Cache: O nó local tenta associar-se a todos os nós cujas informações estão contidas no host cache; • Host Cache: Imprime no shell do Linux as informações sobre todas os nós que estão registrados no host cache. Há eventos que podem ocorrer em tempo de execução, que não têm interação com a interface gráfica: • O recebimento de uma mensagem PONG: Mesmo sendo a mensagem PING gerada por uma chamada da interface gráfica, o recebimento da PONG não provoca nenhuma resposta visual ao usuário; • O recebimento de uma mensagem QUERY: Quando uma mensagem QUERY é detectada, o nó local a processa, conferindo se possui um arquivo local que possa satisfazê-la, e a transfere a todos os nós diretamente conectados, exceto àquele que a enviou, caso o TTL não tenha expirado; • O recebimento de uma mensagem QUERYHIT que carrega uma resposta que não pertence ao nó local: Neste caso, os dados do cache de identificadores de mensagens são utilizados para efetuar o processo do backtrack. 4.4.4 A negociação e a transferência de arquivos no Octopus A associação estabelecida entre dois nós que fazem uso do aplicativo Octopus pode ser utilizada para o envio simultâneo arquivos. Para que esses nós possam negociar essa transferência, um protocolo simples, baseado no HTTP, foi especificado, e é mostrado nesta seção. Quanto o usuário solicita a transferência de um arquivo, uma mensagem de requisição é enviada ao nó que compartilha este arquivo. Esta mensagem é enviada através do fluxo cinco, de forma desordenada, e sua sintaxe é mostrada a seguir: 4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus 110 GET /get/<Nome do arquivo>/ OCTOPUS/1.0\r\n \r\n Para implementar o envio simultâneo de arquivos através de uma única associação, faz-se uso da funcionalidade dos multi-fluxos. A versão do aplicativo desenvolvido limita-se ao envio de três arquivos, de maneira full-duplex, ou seja, três deles podem trafegar em um sentido, e três podem trafegar no sentido oposto, simultaneamente. Os fluxos de número seis, sete ou oito são utilizados para esta tarefa, e as mensagens enviadas através deles são entregues de forma ordenada. Assim que um nó recebe o pedido de transferência de arquivo, ele seleciona um dos três fluxos que estiver disponı́vel, de acordo com o algoritmo mostrado no fluxograma da Figura 4.36, e envia a seguinte mensagem, através do fluxo que selecionou: OCTOPUS/1.0 001 OK\r\n Arquivo:<Nome do Arquivo>\r\n Tamanho:<Tamanho do arquivo>\r\n \r\n O nó requerente, segundo as informações recebidas na mensagem de resposta do nó servidor, sabe o nome do arquivo que deve criar, assim como o fluxo através do qual vai receber as mensagens que compõem este arquivo. Um contador é configurado com o valor do tamanho do arquivo, obtido da mensagem de resposta, que é decrementado a cada porção de dados que é gravada. Quando este contador atinge o valor zero, o arquivo é fechado e o processo de transferência é encerrado. A escolha dos fluxos é feita de maneira seqüencial, como mostrado no fluxograma da Figura 4.36. Primeiramente é avaliada a disponibilidade do fluxo de número seis. Caso ele esteja ocupado, o fluxo de número sete é conferido. Caso ambos estejam ocupados, o fluxo oito é utilizado. Há, porém, a possibilidade de não haver mais fluxos disponı́veis em um dado sentido da associação, pois três arquivos já estão sendo transmitidos simultaneamente. Neste caso, a seguinte mensagem é enviada ao nó requerente, através do fluxo cinco: 4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus 111 Figura 4.36: O fluxograma do servidor de arquivos do Octopus OCTOPUS/1.0 002 ERRO\r\n Arquivo:<Nome do Arquivo>\r\n Motivo:Todos os fluxos ocupados\r\n \r\n Há, também, a possibilidade do arquivo não ser mais encontrado para o compartilhamento, e o servidor envia a seguinte mensagem, através do fluxo cinco: OCTOPUS/1.0 003 ERRO\r\n Arquivo:<Nome do Arquivo>\r\n Motivo:Arquivo n~ ao encontrado\r\n 4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus 112 \r\n O fluxograma da Figura 4.37 mostra os passos para a execução da transferência de arquivos, sob a perspectiva do cliente de arquivos, ou seja, aquele que requisita o arquivo. Figura 4.37: O fluxograma do cliente de arquivos do Octopus Primeiramente o nó envia o pedido do arquivo. Caso não ocorra nenhum erro no servidor de arquivos, causados pela indisponibilidade dos fluxos, ou por algum erro na abertura do arquivo, o nó cliente recebe, através do fluxo selecionado pelo servidor, as informações sobre o arquivo que é enviado, podendo, desta forma, executar 4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus a abertura deste arquivo e atualizar o contador que auxilia no seu fechamento. 113 Capı́tulo 5 Conclusão Os aplicativos peer-to-peer têm sido cada vez mais utilizados na Internet para diversas finalidades. Aqueles aplicativos destinados ao compartilhamento de arquivos são os mais disseminados e empregados pelos usuários. Por este motivo, esse gênero de sistema foi o alvo de estudo e de desenvolvimento deste trabalho. O protocolo de transporte é uma peça fundamental no desenvolvimento desses aplicativos e a sua escolha correta afeta diretamente o desempenho e as funcionalidades que esses sistemas oferecem. Foi mostrada, neste trabalho, a viabilidade da utilização do SCTP como protocolo da camada de transporte para os aplicativos peer-to-peer de compartilhamento de arquivos. Primeiramente, foram apresentadas as definições e os gêneros de sistemas que podem ser desenvolvidos conforme o modelo peer-to-peer, assim como as caracterı́sticas das redes que o implementam. Em seguida, foi apresentada uma classificação das diversas arquiteturas de redes peer-to-peer e exemplos de protocolos da camada de aplicação para aplicativos de compartilhamento de arquivos. Uma das contribuições deste trabalho foi a proposta de uma nova classificação das diferentes arquiteturas de redes peer-to-peer. A partir do levantamento das caracterı́sticas relacionadas à escalabilidade, aos papéis dos nós e aos algoritmos de roteamento, foi apresentada uma forma de classificar as arquiteturas de redes peer-to-peer dentro de um pólo mais centralizador a outro, descentralizador. Em seguida, foram apresentados os conceitos sobre a camada de transporte da 114 Capı́tulo 5. Conclusão 115 arquitetura Internet, envolvendo os protocolos de transporte UDP, TCP e SCTP. Foram mostrados o formato de seus cabeçalhos e os detalhes do estabelecimento e do encerramento de conexões, assim como dos estados de funcionamento, para os protocolos orientados à conexão. Fez-se também um comparativo entre os três protocolos, com ênfase no TCP e no SCTP. Os aplicativos peer-to-peer para compartilhamento de arquivos que apresentam arquiteturas descentralizadas precisam trocar mensagens entre si para encontrar arquivos entre os diversos computadores. Essas mensagens são caracterizadas por possuı́rem tamanhos variáveis e por não restringirem a ordenação de entrega. A natureza do TCP, que restringe a ordem da entrega dos dados à camada de aplicação, pode fazer com que a transmissão de mensagens entre os nós de uma rede peer-to-peer tenha o seu desempenho degradado, causados pelo problema do HOL. O protocolo SCTP possui a funcionalidade da entrega desordenada de mensagens, que pode melhorar o desempenho desta tarefa. Para mostrar esse ganho, foi utilizado um ambiente de simulação composto por dois aplicativos que foram desenvolvidos para simular o trânsito de mensagens entre dois nós de uma rede overlay, tomando como base a dinâmica de uma rede Gnutella real, e um gerador de perda e atraso de pacotes, implementado por meio do aplicativo NIST Net, que emula o comportamento que a Internet provoca nas mensagens transmitidas. Comprovou-se, então, que o SCTP tem um melhor desempenho em relação ao TCP, em redes com altas taxas de perda de pacotes, proporcionando um menor tempo médio de transmissão de mensagens nó-a-nó. Isso se deve à natureza ordenada do TCP que, ao restringir a ordem de entrega das mensagens, degrada o desempenho geral, fato que não ocorre quando o SCTP utiliza a entrega desordenada de mensagens. A utilização dos multi-fluxos nesta simulação teve um caráter ilustrativo, para mostrar que o HOL intra-fluxo pode prejudicar de maneira sutil o desempenho da transmissão de mensagens. Levando em consideração que muitos nós participantes nas redes peer-to-peer, para compartilhamento de arquivos, estão inter-conectados por meios que podem Capı́tulo 5. Conclusão 116 apresentar taxas de perdas de pacotes variadas e que, muitas vezes, estão em regiões geográficas distintas, a utilização da entrega desordenada de mensagens, oferecida pelo SCTP, passa a ser uma opção em potencial para a transmissão das mensagens de busca, satisfazendo as necessidades dos aplicativos peer-to-peer. Assim sendo, uma outra contribuição muito importante deste trabalho foi a análise comparativa de desempenho entre o SCTP e o TCP como protocolos da camada de transporte para aplicativos peer-to-peer de compartilhamento de arquivos. Com a utilização do SCTP, o tempo médio de transmissão de mensagens nó-a-nó é reduzido e, com isso, o tempo médio para a chegada das respostas da fase de consulta é diretamente beneficiado. Para comprovar a viabilidade da utilização do SCTP, em vez do TCP, em um aplicativo peer-to-peer de compartilhamento de arquivos, o protótipo de aplicação Octopus foi desenvolvido. Este aplicativo utiliza os conceitos das redes peer-to-peer, a partir do uso do protocolo Gnutella na camada de aplicação, e do SCTP, que é utilizado na camada de transporte. O objetivo deste desenvolvimento é, por meio deste protótipo, mostrar como algumas funcionalidades do SCTP podem ser aproveitadas em um aplicativo peerto-peer de compartilhamento de arquivos. As funcionalidades exploradas foram a orientação à mensagem, os multi-fluxos e a entrega desordenada de mensagens. Comparativamente ao TCP, é de se esperar um melhor desempenho. Foi implementada no Octopus uma reestruturação do cabeçalho das mensagens Gnutella para ser utilizado com o SCTP. Os delimitadores de mensagem, implementados pelos campos de tamanho de mensagem na versão Gnutella original, podem ser removidos, pois o SCTP oferece a orientação à mensagem. Os identificadores dos tipos de payload também podem ser removidos, pois os identificadores de fluxo podem ser utilizados para essa tarefa, sem necessariamente fazer uso da funcionalidade primária da transmissão de mensagem em um mesmo fluxo, que faz com que a ordenação seja mantida. A funcionalidade da entrega de mensagens desordenadas foi utilizada para o tráfego de mensagens Gnutella. A principal motivação para este uso é a flexibilidade Capı́tulo 5. Conclusão 117 na ordenação de entrega dessas mensagens. O ganho de desempenho no emprego desta funcionalidade foi mostrado na análise comparatira entre o TCP e o SCTP. A funcionalidade dos multi-fluxos foi utilizada para realizar a transmissão simultânea de arquivos, através de uma única associação SCTP, que foi também utilizada para transmitir as mensagens Gnutella. Um protocolo, baseado no HTTP, foi especificado para a negociação da transferência dos arquivos entre os nós. Este desenvolvimento teve como contribuição a apresentação da reestruturação do protocolo Gnutella que faz uso da orientação à mensagem e dos identificadores de fluxo, e a apresentação de um protótipo de aplicativo que mostra como as funcionalidades do SCTP podem ser utilizadas em um aplicativo peer-to-peer de compartilhamento de arquivos. Tem como contribuição, ainda, a especificação de um protocolo simplificado para a negociação da transferência de arquivos. O presente trabalho teve também como contribuição a publicação de três artigos. Dois deles foram apresentados em congressos [9, 10] e referem-se ao uso do SCTP em aplicativos peer-to-peer. O outro refere-se à proposta de classificação das arquiteturas de redes peer-to-peer, e foi aceito em um periódico [11]. Como sugestões para a melhoria deste trabalho, tem-se a execução de testes exaustivos para a obtenção de estatı́sticas mais precisas referentes aos tamanhos e à freqüência de transmissão das mensagens Gnutella, por meio da implementação de um monitor de rede. Com isso, novas execuções de testes comparativos entre o SCTP e o TCP podem ser feitas para validar os experimentos mostrados. Uma sugestão para a melhoria do Octopus seria a atualização da interface do usuário de modo a apresentar outras funcionalidades, tais como a exibição das informações do cache de associações e do host cache diretamente na interface gráfica, além da implementação da mensagem do tipo PUSH. Outra melhoria seria a implementação de um proxy que integra a rede peer-to-peer do Octopus àquela já consolidada pelo protocolo Gnutella tradicional. Como trabalho futuro, é sugerida a especificação e implementação de um protocolo de rede peer-to-peer descentralizada baseada em ultranós que faz uso do SCTP, tendo como referência as funcionalidades exploradas neste trabalho, e que, ainda, Capı́tulo 5. Conclusão 118 faz uso da funcionalidade do multi-homing no nı́vel dos ultranós, de modo a alcançar um melhor desempenho e a tolerância a falhas por meio da redundância de enlace. Sugere-se também a utilização do SCTP sob as redes de arquitetura estruturada. Por fim, sugere-se a implementação de um aplicativo completo que integra, não só o compartilhamento de arquivos, mas também a comunicação entre usuários por meio de texto, áudio e vı́deo, em que os multi-fluxos possam ser amplamente explorados. Referências Bibliográficas [1] Backx, P., Wauters, T., Dhoedt, B., and Demeester, P. A comparison of peer-to-peer architectures. In Eurescom Summit (Heidelberg, Germany, 2002), Broadband Communication Networks Group. [2] Brookshier, D., Govoni, D., Krishnan, N., and Soto, J. C. JXTA: Java P2P Programming. Sams, USA, Março 2002. 0672323664. [3] Carson, M., and Santay, D. NIST Net - A Linux-based Network Emulation Tool. http://snad.ncsl.nist.gov/itg/nistnet/ acessado em 12/11/2005. [4] Center, S. L. A. Iepm. In Internet End-to-end Performance Monitoring. University of Stanford, 2006. http://www-iepm.slac.stanford.edu/ em 09/02/2006. [5] Clarke, I., Sandberg, O., Wiley, B., and Hong, T. W. Freenet: A distributed anonymous information storage and retrieval system. In Proc. of the Workshop on Design Issues in Anonymity and Unobservability, (CA, USA, Julho 2000), pp. 311–320. [6] Coene, L. RFC 3257 - Stream Control Transmission Protocol Applicability Statement, 2002. http://www.sctp.org acessado em 06/08/2005. [7] Crespo, A., and Garcia-Molina, H. Routing indices for peer-to-peer systems. In Proceedings of the 22th International Conference on Distributed Computing Systems (Washington, DC, 2002), 0-7695-1585-1, IEEE Computer Society, pp. 23–34. [8] Daswani, S., and Fisk, A. A. Gnutella udp extension for scalable searches - guess v0.1. Tech. rep., Gnutella Development Forum, Agosto 2002. 119 Referências Bibliográficas 120 [9] do Carmo, G. S. B., and Barbar, J. S. Peer-to-peer usando sctp. In CONTECSI - Congresso Internacional de Gestão de Tecnologia e Sistemas de Informação (São Paulo, SP, Brasil, 2004), TECSI/FEA/USP. [10] do Carmo, G. S. B., and Barbar, J. S. A utilização do protocolo sctp em uma arquitura peer-to-peer. In WebMedia/LA-Web - Simpósio Brasileiro de Sistemas Multimı́dia e Web (Ribeirão Preto, SP, Brasil, 2004), vol. 2, pp. 265– 267. [11] do Carmo, G. S. B., Barbar, J. S., and Coelho, F. B. Uma nova classificação para as arquiteturas de redes peer-to-peer. Revista do IEEE América Latina (2006). Artigo aceito com previsão de publicação em Julho de 2006. [12] et al, S. Internet draft. In Sockets API Extensions for Stream Control Transmission Protocol (Setembro 2004), Network Working Group, The Internet Society. [13] et al, S. Internet-draft. In SCTP Implementer’s Guide (2004), Network Working Group, The Internet Society. [14] Fanning, S. Napster. http://www.napster.com acessado em 25/06/2005. [15] Ge, Z., Figueiredo, D. R., Jaiswal, S., Kurose, J., and Towsley, D. Modeling peer-to-peer file sharing systems. In Proceedings of INFOCOM (San Francisco, California, USA, Abril 2003), Department os Computer Science, University of Massachussets, Amherst. [16] Hargreaves, T. The fasttrack protocol. http://cvs.berlios.de/ - acessado em 24/10/1980, Julho 2004. [17] Kirk, P., Ed. Gnutella a protocol for a revolution, RFC Gnutella 0.4 (2003). http://rfc-gnutella.sourceforge.net acessado em 08/12/2005. [18] Kirk, P., Ed. Gnutella a protocol for a revolution, RFC Gnutella 0.6 (2003). http://rfc-gnutella.sourceforge.net acessado em 08/12/2005. Referências Bibliográficas 121 [19] Kulbak, Y., and Bickson, D. Emule protocol guide. School of Computer Science and Engineering, The Hebrew University of Jerusalem, Israel, Janeiro 2005. [20] Kurose, J. F., and Ross, K. W. Rede de Computadores e a Internet. Addison Wesley, São Paulo, 2003. 8588639181. [21] Lv, Q., Cao, P., Cohen, E., Li, K., and Shenker, S. Search and replication in unstructured peer-to-peer networks. In Proceedings of the 16th International Conference on Supercomputing (New York, USA, 2002), A. Press, Ed., 1-58113-483-5, pp. 82–95. [22] Markatos, E. P. Tracing large-scale peer to peer system: an hour in the life of gnutella. In 2nd IEEE/ACM Int. Symp. on Cluster Computing and the Grid (2002), IEEE Press, Piscataway, NJ, USA. [23] Mathis, M., Mahdavi, J., Floyd, S., and Romanow, A. RFC 2018 - TCP Selective Acknowledgement Options, Outubro 1996. http://www.ietf.org/rfc/rfc2018.txt acessado em 06/08/2005. [24] Maumounkov, P., and Mazières, D. Kademlia: A peer-to-peer information system based on the xor metric. In Proc. of the 1st Intl. Workshop on Peer-to-Peer Systems (2004). [25] McCaleb, J. eDonkeyProtocol, 2005. http://www.edonkey.com acessado em 24/08/2005. [26] Menascé, D. A., and Kanchanapalli, L. Probabilistic scalable p2p resource location services. In ACM SIGMETRICS Performance Evaluation Review (New York, 2002), 0163-5999, ACM Press, pp. 48–58. [27] Merkur. Emule project. http://www.emule-project.net acessado em 25/05/2005. [28] Metrix, J. M. Global napster usage plummets, but new file-sharing alternatives gaining ground. Software Industry Report, 14 (Julho 2001), 5. Referências Bibliográficas 122 [29] Milojicic, D. S., Kalogeraki, V., Lukose, R., Nagaraja, K., Pruyne, J., Richard, B., Rollins, S., and Xu, Z. Peer-to-peer Computing. HP Laboratories, 2002. [30] Ong, L., and Yoakum, J. RFC 3286 - An Introduction to the Stream Control Transmission Protocol, Outubro 2002. http://www.sctp.org acessado em 06/08/2005. [31] Oram, A., Ed. Peer-to-peer, 1 ed. O’Reilly Media, Março 2001. 059600110X. [32] Postel, J. RFC 768 - User Datagram Protocol, Agosto 1980. http://www.faqs.org/rfcs/rfc768.txt acessado em 06/08/2005. [33] Rajamani, R., Kumar, S., and Gupta, N. Sctp versus tcp: Comparing the performance of transport protocols for web traffic. Tech. rep., Computer Sciences Department, University of Wisconsin-Madison, Maio 2002. [34] Ratnasamy, S., Francis, P., Handley, M., Karp, R., and Shenker, S. A scalable content-addressable network. In Proceedings of the 2001 Conference on Applications, Technologies, architectures, and Protocols for Computer Communications (New York, 2001), 1-58113-411-8, ACM Press, pp. 161–172. [35] Ritter, J. Why gnutella can’t scale. no, really. Tech. rep., Darkridge, Fevereiro 2001. www.darkridge.com/ jpr5/doc/gnutella.html acessado em 15/01/2006. [36] Rocha, J., Domingues, M., Callado, A., Souto, E., Silvestre, G., Kamienski, C., and Sadok, D. Peer-to-peer: Computação colaborativa na internet. Tech. rep., P2P Work Group of the Brazilian Research Network, 2004. [37] Rowstron, A., and Druschel, P. Pastry: Scalable, distributed object location and routing for large-scale peer-to-peer systems. In Proceedings of the 18th IFIP ACM International Conference on Distributed Systems Platforms (2001), vol. 2218, Heidelberg, Alemanha, pp. 329–350. [38] Schollmeier, R. A definition of peer-to-peer networking for the classification of peer-to-peer architectures and applications. In Proceedings of the First In- Referências Bibliográficas 123 ternational Conference on Peer-to-Peer Computing (2002), 0-7695-1503-7/02, IEEE Internet Computing. [39] Singla, A., and Rohrs, C. Ultrapeers: Another step towards gnutella scalability. Tech. rep., Lime Wire LLC, 2002. http://limewire.com. [40] Sourceforge.net. LKSCTP Project - Linux Implementation for SCTP, 2005. http://lksctp.sourceforge.net acessado em 14/08/2005. [41] Stewart, R. R., and Xie, Q. RFC 2960 - Stream Control Transmission Protocol, 2000. http://www.sctp.org acessado em 06/08/2005. [42] Stewart, R. R., and Xie, Q. Stream Control Transmission Protocol (SCTP) - A Reference Guide. Addison-Wesley, 2002. 0201721864. [43] Stoica, I., Morris, R., Liben-Nowell, D., Karger, D. R., Kaashoek, M. F., Dabek, F., and Balakrishnan, H. Chord: A scalable peer-topeer lookup protocol for internet applications. In IEEE/ACM Transactions on Networking (2003), vol. 11 of 1063-6692, IEEE Press, Piscataway, NJ, USA, pp. 17 – 32. [44] Strokes, M. Gnutella2 specifications. In Gnutella2 Developer Network. Gnutella Development Network, 2003. http://www.gnutella2.com acessado em 26/01/2006. [45] Tanembaum, A. S. Redes de Computadores. Editora Campus, São Paulo, 2003. 8535211853. [46] Team, D. S. Peer-to-peer Application Development. Hungry Minds, 2002. [47] Tsoumakos, D., and Roussopoulos, N. Adaptive probabilistic search for peer-to-peer networks. In Proc. 3rd International Conf. Peer-to-Peer Computing (P2P 2003) (2003), IEEE CS Press, pp. 102–110. [48] Tsoumakos, D., and Roussopoulos, N. A comparison of peer-to-peer search methods. In International Workshop on the Web and Databases (Junho 2003). Referências Bibliográficas 124 [49] V. Kalogeraki, D. G. e. D. Z.-Y. A local search mechanism for peer-topeer networks. In Proceedings of the 11th International Conference on Information and Knowledge Management (New York, 2002), 1-58113-492-4, ACM Press, pp. 300–307. [50] Yang, B., and Garcia-Molina, H. Comparing hybrid peer-to-peer systems. In Proceedings of the 27th Very Large Data Bases Conference, Roma, Itália (2001), 1-55860-804-4, ACM Press, Morgan Kaufmann Publishers Inc, San Francisco, CA, USA, pp. 561 – 570. [51] Yang, B., and Garcia-Molina, H. Improving search in peer-to-peer networks. In Proceedings of the 22nd International Conference on Distributed Computing Systems (Washington, DC, 2002), 0-7695-1585-1, IEEE Computer Society, pp. 5–14. [52] Yang, B., and Garcia-Molina, H. Designing a super-peer network. In Proceedings of the 19th International Conference on Data Engineering (ICDE), Bangalore, India (Março 2003), Computer Science Department, Stanford University. [53] Zhao, B. Y., Huang, L., Stribling, J., Rhea, S. C., Joseph, A. D., and Kubiatowicz, J. D. Tapestry: A resilient global-scale overlay for service deployment. IEEE Journal on Selected Areas in Communications (Janeiro 2004). Apêndice A Códigos fonte dos aplicativos do ambiente de simulação Os códigos fonte apresentados neste apêndice referem-se aos aplicativos “Gerador e receptor de pacotes”, ou “Nó 1”, e Echo server, ou “Nó 2”, descritos no Capı́tulo 4. /**************************************************************** GeraPacotes.c - Aplicativo gerador e receptor de pacotes Gustavo Salvadori Baptista do Carmo 10 de Fevereiro de 2006 [email protected] ****************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include <sys/types.h> <sys/socket.h> <netinet/in.h> <netinet/tcp.h> <netinet/sctp.h> <arpa/inet.h> <errno.h> <netdb.h> <stdio.h> <stdlib.h> <getopt.h> <string.h> <unistd.h> <time.h> <sys/time.h> <pthread.h> "./Misc.c" //--------------------------------------------------------------//Estruturas de par^ ametros para funç~ oes - threads //--------------------------------------------------------------struct parametroT1 { int sock_fd; struct sockaddr_in endereco; }; //--------------------------------------------------------------//Protótipos //--------------------------------------------------------------//Funç~ oes para controle geral e exibiç~ ao de resultados void uso (void); void cuidaDoLance (void); 125 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação void parser (int argc, char **argv); void calculaTempo (void); void geraScript (void); //Funcoes para criaç~ ao de relacionamentos void criaConexaoTCP (void); void criaAssociacaoSCTP (void); //Funç~ oes para envio e recebimento de mensagens void *enviaMensagensTCP (struct parametroT1 *); void *enviaMensagensSCTP (struct parametroT1 *); void *recebeMensagensTCP (struct parametroT1 *); void *recebeMensagensSCTP (struct parametroT1 *); //Funç~ oes especificas para o TCP int writeN (int fd, const void *buffer, int n); int readN (int fd, void *buffer, int n); //--------------------------------------------------------------//Variáveis globais //--------------------------------------------------------------int fluxos = 0; int fila = 0; char *enderecoLocal = NULL; char *enderecoRemoto = NULL; int portaLocal = 0; int portaRemota = 0; int tamanhoBuffer = 0; int numeroMensagensEnviar = 30; int tipoDeProtocolo = 0; int tamanhoMensagem = 20; int mensagemDesordenada = 0; int intervaloEntreMensagens = 0; int verbose = 1; FILE *arquivo; FILE *arquivo2; //Representaç~ ao de mensagens Gnutella extern char *gnutella[5]; //estrutura enviada via socket struct tipao { int id; char sendline[100]; }; //Estruturas e variáveis de auxı́lio no cálculo de tempo struct paraContagem { int id; int tamanho; struct timeval cronometroIda; struct timeval cronometroVolta; }; struct timeval tempoGlobalInicial; struct timeval tempoGlobalFinal; int quantidadeEnviadaGlobal = 0; struct paraContagem matriz[NUMERO_MAXIMO_MENSAGENS]; //Miscel^ ania double latenciaMedia; double vazaoMedia; double vazaoGeral = 0; double latenciaGeral = 0; double latenciaTotal; double vazaoTotal; double latenciaIndividual[NUMERO_MAXIMO_MENSAGENS]; double vazaoIndividual[NUMERO_MAXIMO_MENSAGENS]; 126 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação //--------------------------------------------------------------//Programa principal //--------------------------------------------------------------int main (int argc, char **argv) { parser (argc, argv); cuidaDoLance (); int i; //Executa o programa por um número definido de vezes if (tipoDeProtocolo == 1) for (i = 1; i <= verbose; i++) { //Protocolo escolhido foi o TCP criaConexaoTCP (); } else for (i = 1; i <= verbose; i++) { //Protocolo escolhido foi o SCTP criaAssociacaoSCTP (); } return 0; } //fim de main //--------------------------------------------------------------//Funç~ ao para preencher os valores da linha de comando //--------------------------------------------------------------void parser (int argc, char **argv) { int getoptC; int bufferAux; int numAux; int tamanhoAux; int desordem; int intervalo; int verb; while (argc) { getoptC = getopt (argc, argv, "H:P:h:p:t:x:f:b:m:s:d:u:v:"); if (getoptC < 0) break; switch (getoptC) { case ’H’: enderecoLocal = optarg; break; case ’P’: portaLocal = atoi (optarg); break; case ’h’: enderecoRemoto = optarg; break; case ’p’: portaRemota = atoi (optarg); break; case ’t’: tipoDeProtocolo = atoi (optarg); break; 127 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação case ’x’: fluxos = atoi (optarg); break; case ’f’: fila = atoi (optarg); break; case ’b’: bufferAux = atoi (optarg); if ((bufferAux > 0) && (bufferAux < 1001)) tamanhoBuffer = bufferAux; break; case ’m’: numAux = atoi (optarg); if ((numAux > 0) && (numAux < 101)) numeroMensagensEnviar = numAux; break; case ’s’: tamanhoAux = atoi (optarg); if ((tamanhoAux > 1) && (tamanhoAux < 101)) tamanhoMensagem = tamanhoAux; break; case ’d’: desordem = atoi (optarg); if (desordem == 1) mensagemDesordenada = MSG_UNORDERED; break; case ’u’: intervalo = atoi (optarg); if ((intervalo > 0) && (intervalo < 1001)) intervaloEntreMensagens = intervalo; break; case ’v’: verb = atoi (optarg); if ((verb > 1) && (verb < 101)) verbose = verb; break; default: break; } } } //fim do switch //fim while argc //fim de parser //--------------------------------------------------------------//Funç~ ao para verificaç~ ao dos par^ ametros de entrada //--------------------------------------------------------------void cuidaDoLance (void) { if ((enderecoLocal == NULL) || (inet_aton (enderecoLocal, NULL) == 0)) { printf ("\nFaltou colocar o endereço local\n"); uso (); exit (0); } if (portaLocal == 0) { printf ("\nFaltou colocar a porta local\n"); uso (); exit (0); } 128 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação if ((enderecoRemoto == NULL) || (inet_aton (enderecoRemoto, NULL) == 0)) { printf ("\nFaltou colocar o endereço remoto\n"); uso (); exit (0); } if (portaRemota == 0) { printf ("\nFaltou colocar a porta remota\n"); uso (); exit (0); } if ((tipoDeProtocolo != 1) && (tipoDeProtocolo != 2)) { printf ("\nEscolha de protocolo incorreta\n"); printf ("\nTCP=1 e SCTP=2\n"); uso (); exit (0); } if ((fluxos < 1) && (tipoDeProtocolo == 2)) { printf ("\nFluxo setado para 1\n"); fluxos = 1; } if ((fila < 1) && (tipoDeProtocolo == 2)) { printf ("\nFila setada para 1\n"); fila = 1; } printf printf printf printf printf ("\n"); ("Endereco ("Porta do ("Endereco ("Porta do do host local:%s\n", enderecoLocal); host local:%d\n", portaLocal); do host remoto:%s\n", enderecoRemoto); host remoto:%d\n", portaRemota); if ((tipoDeProtocolo == 1)) printf ("Protocolo de transporte: TCP\n"); else { //Caso do SCTP printf ("Protocolo de transporte: STCP\n"); printf ("Numero de fluxos:%d\n", fluxos); printf ("Fila de mensagens em cada fluxo:%d\n", fila); if (tamanhoBuffer == 0) printf ("Buffer de envio e recebimento setado para padrao\n"); else printf ("Buffer de envio e recebimento (kBytes):%d\n", tamanhoBuffer); if (mensagemDesordenada == 0) printf ("Entrega das mensagens: ordenada\n"); else printf ("Entrega das mensagens: desordenada\n"); } //Fim do caso SCTP printf ("Numero de mensagens a enviar:%d\n", numeroMensagensEnviar); printf ("Tamanho das mensagens (bytes):%d\n", tamanhoMensagem); printf ("Intervalo entre mensagens (milisegundos):%d\n", intervaloEntreMensagens); printf ("\n"); } //fim cuida do lance 129 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação //--------------------------------------------------------------//Funç~ ao para imprimir o help //--------------------------------------------------------------void uso (void) { printf ("\n"); printf ("Uso: ./GeraPacotes -H endLoc -P ptLoc -h endRem -p ptRem -t tipProt\n"); printf (" -x numFlux -f fila -b buffer -m mensagens -s size\n"); printf (" -d ordem -u tempo -v verbose\n\n"); printf (" -H endLoc - endereço do host local\n"); printf (" -P ptLoc - porta do host local\n"); printf (" -h endRem - endereço do host remoto\n"); printf (" -p ptRem - porta do host remoto\n"); printf (" -t tipProt - protocolo de transporte - TCP=1 - SCTP=2\n"); printf (" -x numFlux - número de fluxos - min 1 - max 300 - SCTP\n"); printf (" -f fila - fila de mensagens em cada fluxo - min 1 - max 5 - SCTP\n"); printf (" -b buffer - buffer de envio e recebimento (kBytes) "); printf ("- min 1 - max 1000 - SCTP\n"); printf (" -m mensagens - número de mensagens a enviar - min 1 - max 100\n"); printf (" -s size - tamanho da mensagem (bytes) - min 2 - max 100\n"); printf (" -d ordem - 1 - envia as mensagens de maneira desordenada\n"); printf (" -u tempo - tempo entre mensagens - milisegundos - padr~ ao 50\n"); printf (" -v verbose - verbose 1 ou 2\n"); printf ("\n"); } //fim de uso //--------------------------------------------------------------//--------------------------------------------------------------void criaAssociacaoSCTP (void) { int sock_fd; int erro; struct sockaddr_in servaddr, meuaddr, enderecoEnviante; struct sctp_event_subscribe evnts; //Criaç~ ao do socket sock_fd = socket (AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP); if (sock_fd < 0) { printf ("Criaç~ ao do socket falhou\n"); exit (0); } //Configurando os endereços bzero (&servaddr, sizeof (servaddr)); servaddr.sin_family = AF_INET; inet_aton (enderecoRemoto, &servaddr.sin_addr); servaddr.sin_port = htons (portaRemota); bzero (&meuaddr, sizeof (meuaddr)); meuaddr.sin_family = AF_INET; inet_aton (enderecoLocal, &meuaddr.sin_addr); meuaddr.sin_port = htons (portaLocal); //Subscrevendo as notificaç~ oes de eventos bzero (&evnts, sizeof (evnts)); evnts.sctp_data_io_event = 1; erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &evnts, sizeof (evnts)); if (erro != 0) { printf ("Erro a setar opcao do socket de initmessage\n"); exit (0); } 130 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação int desliga = 1; //Desligando o Nagle erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_NODELAY, &desliga, sizeof (desliga)); if (erro == (-1)) { printf ("Erro ao desligar o Nagle do SCTP\n"); exit (0); } struct sctp_initmsg initmsg; //Configurando o número de fluxos initmsg.sinit_num_ostreams = fluxos; initmsg.sinit_max_instreams = fluxos; initmsg.sinit_max_attempts = 3; initmsg.sinit_max_init_timeo = 30; erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_INITMSG, (char *) &initmsg, sizeof (struct sctp_initmsg)); if (erro != 0) { printf ("Erro a setar opç~ ao do socket de initmessage\n"); exit (0); } //Configurando o tamanho do buffer tamanhoBuffer = tamanhoBuffer * 1000; if (tamanhoBuffer != 0) { erro = setsockopt (sock_fd, IPPROTO_SCTP, SO_RCVBUF, (char *) &tamanhoBuffer, sizeof (tamanhoBuffer)); if (erro != 0) { printf ("Erro a setar opç~ ao do socket de receive buffer\n"); exit (0); } erro = setsockopt (sock_fd, IPPROTO_SCTP, SO_SNDBUF, (char *) &tamanhoBuffer, sizeof (tamanhoBuffer)); if (erro != 0) { printf ("Erro a setar opç~ ao do socket de send buffer\n"); exit (0); } } //fim tamanhobuffer!=0 //Efetuando o bind erro = bind (sock_fd, (struct sockaddr *) &meuaddr, sizeof (meuaddr)); if (erro != 0) { printf ("Erro ao fazer o bind\n"); exit (0); } //esse trecho é para n~ ao contar o tempo do //estabelecimento de associaç~ ao do SCTP struct sctp_sndrcvinfo sri; struct tipao mensagem; int quantidadeEnviada; 131 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação int quantidadeRecebida; int msg_flags; bzero (&sri, sizeof (sri)); sri.sinfo_stream = 0; int out_sz; socklen_t len; len = sizeof (struct sockaddr_in); char *sendline; sendline = malloc (25); bzero (mensagem.sendline, sizeof (100)); bzero (sendline, sizeof (25)); sprintf (mensagem.sendline, "Conectando"); out_sz = strlen (mensagem.sendline) + sizeof (int); mensagem.id = 1000; quantidadeEnviada = sctp_sendmsg (sock_fd, "Conectando", out_sz, (struct sockaddr *) &servaddr, sizeof (struct sockaddr), 0, mensagemDesordenada, sri.sinfo_stream, 0, 0); if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar a mensagem\n"); } quantidadeRecebida = sctp_recvmsg (sock_fd, sendline, 25, (struct sockaddr *) &enderecoEnviante, &len, &sri, &msg_flags); if (quantidadeRecebida == (-1)) { printf ("Falha ao receber a mensagem\n"); } //fim do trecho de estabelecimento de associaç~ ao SCTP struct parametroT1 valores1; struct parametroT1 valores2; //Preenchendo as estruturas de par^ ametros para as threads valores1.sock_fd = sock_fd; valores1.endereco = enderecoEnviante; valores2.sock_fd = sock_fd; valores2.endereco = servaddr; pthread_t p_thread_envia; pthread_t p_thread_recebe; //Abre a thread que gera e envia mensagens pthread_create (&p_thread_envia, NULL, (void *) enviaMensagensSCTP, &valores2); //Abre a thread que recebe mensagens pthread_create (&p_thread_recebe, NULL, (void *) recebeMensagensSCTP, &valores1); //Aguarda os términos das duas threads pthread_join (p_thread_envia, NULL); pthread_join (p_thread_recebe, NULL); //Calcula e exibe resultados calculaTempo (); geraScript (); 132 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação return; } //fim de cria associaç~ ao SCTP //--------------------------------------------------------------//--------------------------------------------------------------void geraScript (void) { printf ("\n"); printf ("#------------------------------------------------------------------\n"); printf ("#MANUAL DE LEITURA\n"); printf ("#------------------------------------------------------------------\n"); printf printf printf printf printf printf printf printf printf ("\n"); ("#-lat^ encia média: ("#-vaz~ ao média: ("\n"); ("#-lat^ encia geral: ("#-vaz~ ao geral: ("\n"); ("#-lat^ encia total: ("#-vaz~ ao total: printf printf printf printf ("\n"); ("#------------------------------------------------------------------\n"); ("#INFORMAC~ OES GERAIS\n"); ("#------------------------------------------------------------------\n"); printf printf printf printf printf ("\n"); ("#Endereço ("#Porta do ("#Endereço ("#Porta do a média aritmética de todas as lat^ encias individuais\n"); a média aritmética de todas as vaz~ oes individuais\n"); a soma de todas as lat^ encias, n~ ao considerando os intervalos\n"); a soma de todas as vazoes, n~ ao considerando os intervalos\n"); desde a primeira mensagem até a ultima recebida\n"); desde da primeira mensagem enviada ateh a ultima recebida\n"); do host local:%s\n", enderecoLocal); host local:%d\n", portaLocal); do host remoto:%s\n", enderecoRemoto); host remoto:%d\n", portaRemota); if ((tipoDeProtocolo == 1)) printf ("#Protocolo de transporte: TCP\n"); else { //Caso do SCTP printf ("#Protocolo de transporte: STCP\n"); printf ("#Número de fluxos:%d\n", fluxos); printf ("#Fila de mensagens em cada fluxo:%d\n", fila); if (tamanhoBuffer == 0) printf ("#Buffer de envio e recebimento setado para padr~ ao\n"); else printf ("#Buffer de envio e recebimento (kBytes):%d\n", tamanhoBuffer); } //Fim do caso SCTP printf ("#Número de mensagens a enviar:%d\n", numeroMensagensEnviar); printf ("#Tamanho das mensagens (bytes):%d\n", tamanhoMensagem); printf ("\n"); printf ("#Round Trip Time:\n"); printf ("#Taxa de perda de pacotes:\n"); printf printf printf printf printf ("\n"); ("#------------------------------------------------------------------\n"); ("#IdMsg : Lat(s) : Vaz(b/s) : Lat Media(s) : Vaz~ ao Média(b/s) :\n"); ("#------------------------------------------------------------------\n"); ("\n"); printf printf printf printf ("#------------------------------------------------------------------\n"); ("#Lat Geral(s) : Vaz~ ao Geral(b/s) : Lat Total(s) : Vaz Total(b/s)\n"); ("#------------------------------------------------------------------\n"); ("\n"); int i; 133 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação for (i = 0; i < numeroMensagensEnviar; i++) { printf ("%d %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f\n", i, latenciaIndividual[i], vazaoIndividual[i], latenciaMedia, vazaoMedia, latenciaGeral, vazaoGeral, latenciaTotal, vazaoTotal); } } //fim de geraScript //--------------------------------------------------------------//--------------------------------------------------------------void calculaTempo (void) { int i, j; double resultadoTempoSec; double resultadoTempoMicro; double resultadoFinal; double vazao; printf printf printf printf ("\n"); ("-----------------------------------------------------\n"); ("CÁLCULO DE LAT^ ENCIA E VAZ~ AO POR MENSAGEM ENVIADA\n"); ("-----------------------------------------------------\n"); for (i = 0; i < numeroMensagensEnviar; i++) { for (j = 0; j < numeroMensagensEnviar; j++) { if (i == matriz[j].id) { resultadoTempoSec = (matriz[j].cronometroVolta.tv_sec) (matriz[j].cronometroIda.tv_sec); resultadoTempoMicro = (double) (matriz[j].cronometroVolta. tv_usec) (double) (matriz[j].cronometroIda.tv_usec); resultadoFinal = resultadoTempoSec + ((double) resultadoTempoMicro / (double) 1000000); printf ("\n"); printf ("Lat^ encia para mensagem %d = %0.6f segundos\n", i, resultadoFinal); latenciaIndividual[i] = resultadoFinal; vazao = (double) (matriz[j].tamanho) / (double) resultadoFinal; printf ("Vaz~ ao para mensagem %d = %0.6f bytes/seg\n", i, vazao); printf ("Quantidade de dados enviado: %d\n", matriz[j].tamanho); vazaoIndividual[i] = vazao; } } } j = numeroMensagensEnviar; //fim if //fim for 2 //fim for 1 //Cálculo da lat^ encia e vaz~ ao globais resultadoTempoSec = (tempoGlobalFinal.tv_sec) - (tempoGlobalInicial.tv_sec); 134 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação resultadoTempoMicro = (double) (tempoGlobalFinal.tv_usec) (double) (tempoGlobalInicial.tv_usec); resultadoFinal = resultadoTempoSec + ((double) resultadoTempoMicro / (double) 1000000); printf printf printf printf ("\n"); ("-----------------------------------------------------\n"); ("\n"); ("Lat^ encia Total Final = %0.6f segundos\n", resultadoFinal); latenciaTotal = resultadoFinal; vazao = (double) (quantidadeEnviadaGlobal) / (double) resultadoFinal; printf ("Quantidade de dados enviados: %d bytes\n", quantidadeEnviadaGlobal); printf ("Vazao Total Final = %0.6f bytes/seg\n\n", vazao); vazaoTotal = vazao; for (i = 0; i < numeroMensagensEnviar; i++) { latenciaGeral = latenciaIndividual[i] + latenciaGeral; vazaoGeral = vazaoIndividual[i] + vazaoGeral; } printf ("Lat^ encia Geral = %0.6f seg\n\n", latenciaGeral); latenciaMedia = (double) latenciaGeral / (double) numeroMensagensEnviar; printf ("Lat^ encia Média = %0.6f seg\n\n", latenciaMedia); printf ("Vaz~ ao Geral = %0.6f bytes/seg\n\n", vazaoGeral); vazaoMedia = (double) vazaoGeral / (double) numeroMensagensEnviar; printf ("Vaz~ ao Media = %0.6f bytes/seg\n\n", vazaoMedia); } //fim de calculaTempo //--------------------------------------------------------------//Funç~ ao que gera e envia mensagens através do SCTP //--------------------------------------------------------------void * enviaMensagensSCTP (struct parametroT1 *variaveis) { printf ("Entrei na thread de ENVIAR mensagens\n"); struct tipao mensagem; int sock_fd = variaveis->sock_fd; struct sockaddr_in servaddr = variaveis->endereco; int loop; int indiceFluxos = 0; int out_sz; int indiceFila = fila; int indiceMensagem = 0; int quantidadeEnviada; struct sctp_sndrcvinfo sri; 135 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação //Essa estrutura é utilizada para saber em qual fluxo //a mensagem veio bzero (&sri, sizeof (sri)); sri.sinfo_stream = 0; printf printf printf printf ("\n"); ("-----------------------------------------------------\n"); ("DADOS SOBRE O ENVIO DE MENSAGENS\n"); ("-----------------------------------------------------\n"); //loop de envio de mensagens for (loop = 0; loop < numeroMensagensEnviar; loop++) { sri.sinfo_stream = indiceFluxos; bzero (mensagem.sendline, sizeof (100)); snprintf (mensagem.sendline, tamanhoMensagem, gnutella[loop % 5]); out_sz = strlen (mensagem.sendline) + 1; mensagem.id = loop; printf printf printf printf printf printf printf ("\n"); ("ENVIEI\n"); ("ID mensagem:%d\n", mensagem.id); ("Mensagem:%s\n", mensagem.sendline); ("Numero Fluxo:%d\n", indiceFluxos); ("Indice Fila:%d\n", indiceFila); ("Numero Loop:%d\n", loop); //Tempo entre as mensagens em milisegundos usleep (1000 * intervaloEntreMensagens); //Inı́cio do cálculo do tempo de ida da mensagem matriz[loop].id = mensagem.id; gettimeofday (&matriz[loop].cronometroIda, NULL); //Enviando a mensagem quantidadeEnviada = sctp_sendmsg (sock_fd, &mensagem, out_sz, (struct sockaddr *) &servaddr, sizeof (struct sockaddr), 0, mensagemDesordenada, sri.sinfo_stream, 0, 0); if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar mensagem\n"); } //Para cálculo do total de bytes enviados quantidadeEnviadaGlobal = quantidadeEnviadaGlobal + quantidadeEnviada; printf ("Bytes enviados:%d\n", quantidadeEnviada); matriz[loop].tamanho = quantidadeEnviada; //Controle de fila nos fluxos if (indiceFila == 1) { if (indiceFluxos == (fluxos - 1)) { indiceFluxos = 0; } else { indiceFluxos++; } indiceFila = fila; } else indiceFila--; 136 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação if (indiceMensagem == 4) indiceMensagem = 0; else indiceMensagem++; } //fim do loop for de envio de mensagens return 0; } //fim de envioMensagens //--------------------------------------------------------------//Funç~ ao que recebe as mensagens através do SCTP //--------------------------------------------------------------void * recebeMensagensSCTP (struct parametroT1 *variaveis) { printf ("Entrei na thread de RECEBER mensagens\n"); //Utilizado para receber a mensagem struct tipao mensagem; //Preenchendo as variáveis locais com os dados //do par^ ametro int sock_fd = variaveis->sock_fd; struct sockaddr_in enderecoEnviante = variaveis->endereco; int loop; int quantidadeRecebida; struct sctp_sndrcvinfo sri; char *sendline; sendline = malloc (105); socklen_t len; len = sizeof (struct sockaddr_in); int msg_flags; //Estrutura utilizada para saber em qual fluxo //a mensagem veio bzero (&sri, sizeof (sri)); sri.sinfo_stream = 0; printf printf printf printf ("\n"); ("-----------------------------------------------------\n"); ("DADOS SOBRE A RECEPÇ~ AO DE MENSAGENS\n"); ("-----------------------------------------------------\n"); //loop de recepç~ ao de mensagens for (loop = 0; loop < numeroMensagensEnviar; loop++) { bzero (sendline, 105); //Recebendo a mensagem quantidadeRecebida = sctp_recvmsg (sock_fd, sendline, 105, (struct sockaddr *) &enderecoEnviante, &len, &sri, &msg_flags); //Armazenando o momento da chegada da mensagem gettimeofday (&matriz[loop].cronometroVolta, NULL); bzero (mensagem.sendline, 100); memcpy (&mensagem, sendline, quantidadeRecebida); matriz[loop].id = mensagem.id; if (quantidadeRecebida == (-1)) { printf ("Falha ao receber a mensagem\n"); loop = loop - 1; 137 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação continue; } if (loop == numeroMensagensEnviar - 1) { tempoGlobalInicial = matriz[0].cronometroIda; tempoGlobalFinal = matriz[loop].cronometroVolta; } //Impress~ ao de dados para recebimento printf ("\n"); printf ("RECEBI\n"); printf ("ID=%d - Mensagem:%s\n", mensagem.id, mensagem.sendline); printf ("Numero Fluxo:%d\n", sri.sinfo_stream); printf ("Quantidade recebida:%d\n", quantidadeRecebida); } //fim do loop de recepç~ ao de mensagens free (sendline); close (sock_fd); return 0; } //fim recebe mensagens //--------------------------------------------------------------//Funç~ ao para enviar N bytes através do TCP //--------------------------------------------------------------int writeN (int fd, const void *buffer, int n) { int nRestante; int nEscritos; const char *ptr; ptr = buffer; nRestante = n; while (nRestante > 0) { if ((nEscritos = write (fd, ptr, nRestante)) <= 0) { if (nEscritos < 0 && errno == EINTR) nEscritos = 0; else return (-1); } nRestante -= nEscritos; ptr += nEscritos; } return n; } //fim do writeN //--------------------------------------------------------------//Funç~ ao para receber N bytes através do TCP //--------------------------------------------------------------int readN (int fd, void *buffer, int n) { int nRestante; int nLidos; char *ptr; ptr = buffer; nRestante = n; 138 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação while (nRestante > 0) { if ((nLidos = read (fd, ptr, nRestante)) < 0) { if (errno == EINTR) nLidos = 0; else return (-1); } else if (nLidos == 0) break; nRestante -= nLidos; ptr += nLidos; } return (n - nRestante); } //fim do readN //--------------------------------------------------------------//Funç~ ao para criar a conex~ ao TCP com o echo server //--------------------------------------------------------------void criaConexaoTCP (void) { int sock_fd; int erro; struct sockaddr_in servaddr, meuaddr; //Abre o socket sock_fd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock_fd < 0) { printf ("Criaç~ ao do socket falhou\n"); exit (0); } int desliga = 1; //Desliga o Nagle erro = setsockopt (sock_fd, IPPROTO_TCP, TCP_NODELAY, &desliga, sizeof (desliga)); if (erro == (-1)) { printf ("Falha ao desligar o Nagle do TCP\n"); } //Definindo os endereços bzero (&servaddr, sizeof (servaddr)); servaddr.sin_family = AF_INET; inet_aton (enderecoRemoto, &servaddr.sin_addr); servaddr.sin_port = htons (portaRemota); bzero (&meuaddr, sizeof (meuaddr)); meuaddr.sin_family = AF_INET; inet_aton (enderecoLocal, &meuaddr.sin_addr); meuaddr.sin_port = htons (portaLocal); //Tentando conectar ao echo server erro = connect (sock_fd, (struct sockaddr *) &servaddr, sizeof (servaddr)); if (erro < 0) { printf ("Erro ao efetuar o connect\n"); exit (0); 139 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação } struct parametroT1 valores1; struct parametroT1 valores2; valores1.sock_fd = sock_fd; valores2.sock_fd = sock_fd; pthread_t p_thread_envia; pthread_t p_thread_recebe; //Cria a thread que envia mensagens pthread_create (&p_thread_envia, NULL, (void *) enviaMensagensTCP, &valores2); //Cria a thread que recebe mensagens pthread_create (&p_thread_recebe, NULL, (void *) recebeMensagensTCP, &valores1); //Aguarda as duas threads terminarem pthread_join (p_thread_envia, NULL); pthread_join (p_thread_recebe, NULL); //Calcula resultados e os exibe calculaTempo (); geraScript (); return; } //fim de cria conexao TCP //--------------------------------------------------------------//Funç~ ao para o envio de mensagens através do TCP //--------------------------------------------------------------void * enviaMensagensTCP (struct parametroT1 *variaveis) { struct tipao mensagem; int int int int sock_fd = variaveis->sock_fd; out_sz; loop; quantidadeEnviada; printf printf printf printf ("\n"); ("-----------------------------------------------------\n"); ("DADOS DE ENVIO DE MENSAGENS\n"); ("-----------------------------------------------------\n"); //loop de envio de mensagens for (loop = 0; loop <= numeroMensagensEnviar; loop++) { bzero (mensagem.sendline, sizeof (100)); snprintf (mensagem.sendline, tamanhoMensagem, gnutella[loop % 5]); out_sz = strlen (mensagem.sendline) + 1; mensagem.id = loop; //Impress~ ao de dados sobre o envio printf ("\n"); printf ("ENVIEI\n"); printf ("ID mensagem:%d\n", mensagem.id); printf ("Mensagem:%s\n", mensagem.sendline); printf ("Número Loop:%d\n", loop); //Inı́cio do cálculo do tempo de ida da mensagem matriz[loop].id = mensagem.id; gettimeofday (&matriz[loop].cronometroIda, NULL); usleep (1000 * intervaloEntreMensagens); 140 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação quantidadeEnviada = writeN (sock_fd, &mensagem, out_sz); if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar mensagem\n"); } //Para cálculo do total de bytes enviados quantidadeEnviadaGlobal = quantidadeEnviadaGlobal + quantidadeEnviada; printf ("Bytes enviados:%d\n", quantidadeEnviada); matriz[loop].tamanho = quantidadeEnviada; } return 0; } //fim do loop for de envio de mensagens //fim de envia mensagens //--------------------------------------------------------------//Funç~ ao que recebe mensagens através do TCP //--------------------------------------------------------------void * recebeMensagensTCP (struct parametroT1 *variaveis) { struct tipao mensagem; int sock_fd = variaveis->sock_fd; int loop; socklen_t len; int quantidadeRecebida; len = sizeof (struct sockaddr_in); char *sendline; sendline = malloc (105); printf printf printf printf ("\n"); ("-----------------------------------------------------\n"); ("DADOS DE RECEPCAO DE MENSAGENS\n"); ("-----------------------------------------------------\n"); //loop de recepç~ ao de mensagens for (loop = 0; loop <= numeroMensagensEnviar; loop++) { bzero (sendline, 105); quantidadeRecebida = readN (sock_fd, sendline, matriz[loop].tamanho); gettimeofday (&matriz[loop].cronometroVolta, NULL); if (quantidadeRecebida == (-1)) { printf ("Falha ao receber a mensagem\n"); loop = loop - 1; continue; } if (quantidadeRecebida == (0)) { loop = loop - 1; continue; } bzero (mensagem.sendline, 100); memcpy (&mensagem, sendline, quantidadeRecebida); if (loop == numeroMensagensEnviar - 1) { tempoGlobalInicial = matriz[0].cronometroIda; 141 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação tempoGlobalFinal = matriz[loop].cronometroVolta; } //Impress~ ao de dados para recebimento printf ("\n"); printf ("RECEBI\n"); printf ("ID=%d - Mensagem:%s\n", mensagem.id, mensagem.sendline); printf ("Quantidade Recebida: %d\n", quantidadeRecebida); printf ("Quantidade que era pra receber: %d\n", matriz[loop].tamanho); } //fim do loop de recepç~ ao de mensagens free (sendline); close (sock_fd); return 0; } //fim de recebe mensagens //--------------------------------------------------------------//Fim de GeraPacotes.c //--------------------------------------------------------------- 142 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação /**************************************************************** EchoServer.c - Echo Server Gustavo Salvadori Baptista do Carmo 10 de Fevereiro de 2006 [email protected] ****************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include <sys/types.h> <sys/socket.h> <arpa/inet.h> <netinet/in.h> <netinet/sctp.h> <netinet/tcp.h> <errno.h> <netdb.h> <stdio.h> <stdlib.h> <unistd.h> <string.h> //--------------------------------------------------------------//Protótipos //--------------------------------------------------------------void uso (void); void parser (int argc, char **argv); void cuidaDoLance (void); void echoSCTP (void); void echoTCP (void); int writeN (int fd, const void *buffer, int n); //--------------------------------------------------------------//Variáveis globais //--------------------------------------------------------------int tamanhoBuffer = 0; int portaLocal = 0; char *enderecoLocal = NULL; int mensagemDesordenada = 0; int tipoProtocolo = 1; //Estrutura usada para receber mensagens struct tipao { int id; char sendline[100]; } mensagem; //--------------------------------------------------------------//Programa principal //--------------------------------------------------------------int main (int argc, char **argv) { parser (argc, argv); cuidaDoLance (); if (tipoProtocolo == 1) echoTCP (); else if (tipoProtocolo == 2) echoSCTP (); else uso (); return 0; } 143 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação //--------------------------------------------------------------//EchoServer com o TCP //--------------------------------------------------------------void echoTCP () { int sock_fd, sock_novo; int erro; struct sockaddr_in servaddr, cliaddr; sock_fd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock_fd < 0) { printf ("Criaç~ ao do socket falhou\n"); exit (0); } bzero (&servaddr, sizeof (servaddr)); servaddr.sin_family = AF_INET; inet_aton (enderecoLocal, &servaddr.sin_addr); servaddr.sin_port = htons (portaLocal); int desliga = 1; erro = setsockopt (sock_fd, IPPROTO_TCP, TCP_NODELAY, &desliga, sizeof (desliga)); if (erro == (-1)) { printf ("Falha ao desligar o Nagle do TCP\n"); } erro = bind (sock_fd, (struct sockaddr *) &servaddr, sizeof (servaddr)); if (erro != 0) { printf ("Erro ao dar o bind\n"); exit (0); } erro = listen (sock_fd, 1); if (erro != 0) { printf ("Erro ao entrar no listen\n"); exit (0); } socklen_t len; len = sizeof (struct sockaddr_in); while (1) { sock_novo = accept (sock_fd, (struct sockaddr *) &cliaddr, &len); if (sock_novo < 0) { printf ("Erro no accept\n"); exit (0); } char *readbuf; int quantidadeEnviada; int quantidadeRecebida; readbuf = malloc (100); 144 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação printf ("----------------------------------------------\n"); printf ("DADOS DA RECEPÇ~ AO\n"); printf ("----------------------------------------------\n"); while (1) { bzero (readbuf, 100); //Recebendo o segmento do cliente quantidadeRecebida = read (sock_novo, readbuf, 100); if (quantidadeRecebida == (-1)) { printf ("Falha ao receber os bytes\n"); } //O cliente encerra a comunicaç~ ao if (quantidadeRecebida == 0) { printf ("\nCliente encerrou a comunicaç~ ao\n\n"); close (sock_novo); free (readbuf); break; } printf ("\n"); printf ("Número de bytes recebidos:%d\n", quantidadeRecebida); printf ("Endereço remoto:%s\n", inet_ntoa (cliaddr.sin_addr)); //Enviando o segmento devolta para o cliente quantidadeEnviada = writeN (sock_novo, readbuf, quantidadeRecebida); if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar os bytes\n"); } } //fim do while 1 } return; } //Fim de Echo Server TCP //--------------------------------------------------------------//EchoServer com o SCTP //--------------------------------------------------------------void echoSCTP () { int sock_fd; int erro; struct sockaddr_in servaddr, cliaddr; struct sctp_sndrcvinfo sri; struct sctp_event_subscribe evnts; sock_fd = socket (AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP); if (sock_fd < 0) { printf ("Criaç~ ao do socket falhou\n"); exit (0); } bzero (&servaddr, sizeof (servaddr)); servaddr.sin_family = AF_INET; inet_aton (enderecoLocal, &servaddr.sin_addr); servaddr.sin_port = htons (portaLocal); 145 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação bzero (&evnts, sizeof (evnts)); evnts.sctp_data_io_event = 1; erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &evnts, sizeof (evnts)); if (erro != 0) { printf ("Erro ao setar a opç~ ao setar eventos\n"); exit (0); } struct sctp_initmsg initmsg; initmsg.sinit_num_ostreams = 30; initmsg.sinit_max_instreams = 30; initmsg.sinit_max_attempts = 3; initmsg.sinit_max_init_timeo = 30; int desliga = 1; erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_NODELAY, &desliga, sizeof (desliga)); if (erro == (-1)) { printf ("Erro ao desligar o Nagle do SCTP\n"); exit (0); } erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_INITMSG, (char *) &initmsg, sizeof (struct sctp_initmsg)); if (erro != 0) { printf ("Erro ao setar a opç~ ao de initmessage\n"); exit (0); } tamanhoBuffer = tamanhoBuffer * 1000; if (tamanhoBuffer != 0) { erro = setsockopt (sock_fd, IPPROTO_SCTP, SO_RCVBUF, (char *) &tamanhoBuffer, sizeof (tamanhoBuffer)); if (erro != 0) { printf ("Erro a setar opç~ ao do socket de receive buffer\n"); exit (0); } erro = setsockopt (sock_fd, IPPROTO_SCTP, SO_SNDBUF, (char *) &tamanhoBuffer, sizeof (tamanhoBuffer)); if (erro != 0) { printf ("Erro a setar opç~ ao do socket de send buffer\n"); exit (0); } } //fim tamanho buffer erro = bind (sock_fd, (struct sockaddr *) &servaddr, sizeof (servaddr)); if (erro != 0) { printf ("Erro ao dar o bind\n"); 146 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação exit (0); } erro = listen (sock_fd, 1); if (erro != 0) { printf ("Erro ao setar a entrar no listen\n"); exit (0); } socklen_t len; int msg_flags; char *readbuf; int quantidadeEnviada; int quantidadeRecebida; len = sizeof (struct sockaddr_in); readbuf = malloc (105); while (1) { bzero (readbuf, 105); //Recebendo a mensagem do cliente quantidadeRecebida = sctp_recvmsg (sock_fd, readbuf, 105, (struct sockaddr *) &cliaddr, &len, &sri, &msg_flags); if (quantidadeRecebida == (-1)) { printf ("Falha ao receber a mensagem\n"); } bzero (mensagem.sendline, 100); memcpy (&mensagem, readbuf, quantidadeRecebida); //DADOS PARA CONFER^ ENCIA printf ("\n"); printf ("Id:%d\n", mensagem.id); printf ("buffer:%s\n", mensagem.sendline); printf ("Numero da stream:%d\n", sri.sinfo_stream); printf ("Numero de bytes recebidos:%d\n", quantidadeRecebida); printf ("Endereco remoto:%s\n", inet_ntoa (cliaddr.sin_addr)); //enviando a mensagem devolta para o cliente quantidadeEnviada = sctp_sendmsg (sock_fd, readbuf, quantidadeRecebida, (struct sockaddr *) &cliaddr, sizeof (struct sockaddr), sri.sinfo_ppid, mensagemDesordenada, sri.sinfo_stream, 0, 0); if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar a mensagem\n"); } } //fim do while 1 free (readbuf); return; } //fim de Echo Server SCTP //--------------------------------------------------------------//Conferindo os dados da linha de comando //--------------------------------------------------------------void cuidaDoLance (void) { if ((enderecoLocal == NULL) || 147 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação (inet_aton (enderecoLocal, NULL) == 0)) { printf ("\nFaltou colocar o endereco local\n"); uso (); exit (0); } if (portaLocal == 0) { printf ("\nFaltou colocar a porta local\n"); uso (); exit (0); } printf printf printf printf ("\n"); ("Endereco do host local:%s\n", enderecoLocal); ("Porta do host local:%d\n", portaLocal); ("Protocolo de transporte = "); if (tipoProtocolo == 1) printf ("TCP\n"); else printf ("SCTP\n"); if (tamanhoBuffer == 0) printf ("Buffer de envio e recebimento setado para padrao\n"); else printf ("Buffer de envio e recebimento (Kbytes):%d\n", tamanhoBuffer); if (mensagemDesordenada == 0) printf ("Entrega das mensagens: ordenada\n"); else printf ("Entrega das mensagens: desordenada\n"); printf ("\n"); } //termina cuida do lance //--------------------------------------------------------------//Preenchendo com os dados da linha de comando //--------------------------------------------------------------void parser (int argc, char **argv) { int getoptC; int bufferAux; int desordem; int protocolo; while (argc) { getoptC = getopt (argc, argv, "H:P:t:b:d:"); if (getoptC < 0) break; switch (getoptC) { case ’H’: enderecoLocal = optarg; break; case ’P’: portaLocal = atoi (optarg); break; case ’t’: protocolo = atoi (optarg); if ((protocolo == 1) || (protocolo == 2)) tipoProtocolo = protocolo; 148 Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação break; case ’b’: bufferAux = atoi (optarg); if ((bufferAux > 0) && (bufferAux < 1001)) tamanhoBuffer = bufferAux; break; case ’d’: desordem = atoi (optarg); if (desordem == 1) mensagemDesordenada = MSG_UNORDERED; break; default: break; } } } //fim do switch //fim while argc //fim de parser //--------------------------------------------------------------//Imprime uso do aplicativo //--------------------------------------------------------------void uso (void) { printf ("\nUso:\n"); printf ("./EchoServer -H endLoc -P ptLoc -t tipProt -b buffer -d ordem\n"); printf (" -H endLoc - endereco do host local\n"); printf (" -P ptLoc - porta do host local\n"); printf (" -t tipProt - protocolo de transporte - TCP=1 - SCTP=2\n"); printf (" -b buffer - buffer de envio e recebimento (Kbytes) "); printf ("- min 1 - max 1000\n"); printf (" -d ordem - 1 - envia as mensagens de forma desordenada\n"); printf ("\n"); } //fim de uso //--------------------------------------------------------------//Funç~ ao para forçar a escrita de todos os bytes TCP //--------------------------------------------------------------int writeN (int fd, const void *buffer, int n) { int nRestante; int nEscritos; const char *ptr; ptr = buffer; nRestante = n; while (nRestante > 0) { if ((nEscritos = write (fd, ptr, nRestante)) <= 0) { if (nEscritos < 0 && errno == EINTR) nEscritos = 0; else return (-1); } nRestante -= nEscritos; ptr += nEscritos; } return n; } //fim de writeN //--------------------------------------------------------------//Fim de EchoServer.c //--------------------------------------------------------------- 149 Apêndice B Código fonte do aplicativo Octopus O código fonte apresentado neste apêndice refere-se ao aplicativo Octopus, descrito no Capı́tulo 4. /**************************************************************** octopus.c - Interface grafica do aplicativo Octopus Versao 1.0 Gustavo Salvadori Baptista do Carmo 27 de Fevereiro de 2006 [email protected] ****************************************************************/ #include #include #include #include #include #include #include </usr/include/gtk-2.0/gtk/gtk.h> <pthread.h> <unistd.h> <sys/dir.h> <sys/param.h> <time.h> "./SERVENTE.c" //----------------------------------------------------------------//Headers das funcoes locais //----------------------------------------------------------------void *fazGraficos (void); void delete_event (void); static void associa (GtkWidget * widget, GtkWidget * entry); static void enviaDownload (GtkWidget * widget, GtkWidget * entry); void selectionMade (GtkWidget * clist, gint row, gint column,gpointer data); GtkWidget *image_new_from_file_at_size (const char *filename, gint width, gint height); //----------------------------------------------------------------//Headers das funcoes do SERVENTE.c //----------------------------------------------------------------void *abreServidor (struct sockaddr_in *servaddr); void imprimeAssociacoesAbertas (void); void enviaQUERY (char *consulta); void enviaPING (void); void pedeArquivo (char *nomeArquivo, char *endereco, char *porta,char *tamanho); int enderecoIgual (struct sockaddr_in endereco1,struct sockaddr_in endereco2); int associaPeer (struct sockaddr_in); void associaHostCache (void); void imprimeHostCache (void); int incluiHostCache (struct sockaddr_in); 150 Apêndice B. Código fonte do aplicativo Octopus //----------------------------------------------------------------//Variáveis globais //----------------------------------------------------------------struct sockaddr_in enderecoConectar; struct sockaddr_in meuaddr; GtkWidget *porta; GtkWidget *listaRemota; gint linhaListaLocal = 500; gint colunaListaLocal = 0; extern int emDownload; //----------------------------------------------------------------//Programa principal //----------------------------------------------------------------int main (int argc, char *argv[]) { int porta; if (argc != 3) { printf ("./octopus Endereco Porta\n"); exit (0); } porta = atoi (argv[2]); //Endereco local do aplicativo bzero (&meuaddr, sizeof (meuaddr)); meuaddr.sin_family = AF_INET; inet_aton (argv[1], &meuaddr.sin_addr); meuaddr.sin_port = htons (porta); //Criando a thread do servidor pthread_t p_thread_servidor; pthread_create (&p_thread_servidor, NULL, (void *) abreServidor, &meuaddr); //Criando a thread da interface grafica pthread_t p_thread_graficos; pthread_create (&p_thread_graficos, NULL, fazGraficos (), NULL); return 0; } //fim de main //-----------------------------------------------------------------//Funcao de evento do botao associar //-----------------------------------------------------------------static void associa (GtkWidget * widget, GtkWidget * entry) { const gchar *enderecoTexto; const gchar *portaTexto; struct sockaddr_in servaddr; int erro; enderecoTexto = gtk_entry_get_text (GTK_ENTRY (entry)); portaTexto = gtk_entry_get_text (GTK_ENTRY (porta)); if ((strcmp (portaTexto, "") == 0)|| (strcmp (enderecoTexto, "") == 0)) { printf ("Nenhum endereco digitado para a associacao\n"); return; } bzero (&servaddr, sizeof (servaddr)); servaddr.sin_family = AF_INET; inet_aton (enderecoTexto, &servaddr.sin_addr); servaddr.sin_port = htons (atoi (portaTexto)); 151 Apêndice B. Código fonte do aplicativo Octopus if (widget == widget) { }; if (strcmp (enderecoTexto, inet_ntoa (meuaddr.sin_addr)) == 0) printf ("Associar a si mesmo nao pode\n"); else { erro = associaPeer (servaddr); if (erro == 1) incluiHostCache (servaddr); gtk_entry_set_text (GTK_ENTRY (entry), ""); gtk_entry_set_text (GTK_ENTRY (porta), ""); } } //Fim de associa //----------------------------------------------------------------//Funcao para limpar a lista de respostas //----------------------------------------------------------------void limpaLista (GtkWidget * widget, GtkWidget * entry) { if (widget == widget) { }; gtk_clist_clear (GTK_CLIST (entry)); linhaListaLocal = 500; } //----------------------------------------------------------------//Funcao de controle da lista de respostas //----------------------------------------------------------------void selection_made (GtkWidget * clist, gint row, gint column, gpointer data) { if (clist == clist) { }; if (data == data) { }; linhaListaLocal = row; colunaListaLocal = column; return; } //----------------------------------------------------------------//Funcao que envia o pedido de transferencia de arquivo //----------------------------------------------------------------static void enviaDownload (GtkWidget * widget, GtkWidget * entry) { if (widget == widget) { }; gchar gchar gchar gchar *nomeArquivo; *endereco; *porta; *tamanho; gtk_clist_get_text (GTK_CLIST (entry), linhaListaLocal, 0, &nomeArquivo); gtk_clist_get_text (GTK_CLIST (entry), linhaListaLocal, 1, &endereco); gtk_clist_get_text (GTK_CLIST (entry), linhaListaLocal, 2, &porta); 152 Apêndice B. Código fonte do aplicativo Octopus gtk_clist_get_text (GTK_CLIST (entry), linhaListaLocal, 3, &tamanho); if (linhaListaLocal != 500) pedeArquivo (nomeArquivo, endereco, porta, tamanho); } //fim de enviaDownload //----------------------------------------------------------------//Funcao que envia uma consulta para todos os nos associados //----------------------------------------------------------------static void enviaConsulta (GtkWidget * widget, GtkWidget * entry) { if (widget == widget) { }; const gchar *consultaTexto; bzero (&consultaTexto, sizeof (gchar)); consultaTexto = gtk_entry_get_text (GTK_ENTRY (entry)); printf ("A string de consulta:%s\n", consultaTexto); if (strlen (consultaTexto) < 4) printf ("Consulta muito curta; use mais de 3 letras\n"); else { enviaQUERY ((char *) consultaTexto); } } //fim de enviaConsulta //----------------------------------------------------------------//Funç~ ao evento de clique no X da janela //----------------------------------------------------------------void delete_event () { exit (0); } //----------------------------------------------------------------//Funcao para a exibicao da interface grafica //----------------------------------------------------------------void * fazGraficos () { GtkWidget *janelaGeral; GtkWidget *logo; GtkWidget *letreiro; GtkWidget *fixed; GtkWidget *botaoAssocia; GtkWidget *botaoDownload; GtkWidget *botaoConsulta; GtkWidget *botaoAssociacao; GtkWidget *botaoPing; GtkWidget *botaoLimpa; GtkWidget *endereco; GtkWidget *consulta; GtkWidget *labelEndereco; GtkWidget *labelPorta; GtkWidget *labelConsulta; GtkWidget *botaoAssociaHostCache; GtkWidget *botaoImprimeHostCache; gtk_init (NULL, NULL); gchar *titles[] = { "Nome do arquivo", "Endereco", "Porta", "Tamanho" }; //Criando os widgets janelaGeral = gtk_window_new (GTK_WINDOW_TOPLEVEL); 153 Apêndice B. Código fonte do aplicativo Octopus logo = image_new_from_file_at_size ("./logo.png", 150, 150); letreiro = image_new_from_file_at_size ("./letreiro.png", 150, 180); fixed = gtk_fixed_new (); botaoAssocia = gtk_button_new_with_label ("Associar"); botaoDownload = gtk_button_new_with_label ("Download"); botaoConsulta = gtk_button_new_with_label ("Consultar"); botaoAssociaHostCache = gtk_button_new_with_label ("Associa Host Cache"); botaoImprimeHostCache = gtk_button_new_with_label ("Host Cache"); botaoPing = gtk_button_new_with_label ("Ping"); botaoLimpa = gtk_button_new_with_label ("Limpa"); botaoAssociacao = gtk_button_new_with_label ("Associacoes"); listaRemota = gtk_clist_new_with_titles (4, titles); consulta = gtk_entry_new (); endereco = gtk_entry_new (); porta = gtk_entry_new (); labelEndereco = gtk_label_new ("Endereco"); labelPorta = gtk_label_new ("Porta"); labelConsulta = gtk_label_new ("Consulta"); //Configurando gtk_window_set_title (GTK_WINDOW (janelaGeral), "oCToPuS"); gtk_window_set_position (GTK_WINDOW (janelaGeral), GTK_WIN_POS_CENTER); gtk_window_set_resizable (GTK_WINDOW (janelaGeral), FALSE); gtk_widget_set_size_request (janelaGeral, 950, 700); gtk_clist_set_selection_mode (GTK_CLIST (listaRemota), GTK_SELECTION_BROWSE); gtk_clist_set_shadow_type (GTK_CLIST (listaRemota), GTK_SHADOW_IN); //Posicionando gtk_fixed_put (GTK_FIXED (fixed), labelEndereco, 300, 30); gtk_fixed_put (GTK_FIXED (fixed), labelPorta, 550, 30); gtk_widget_set_size_request (endereco, 200, 40); gtk_fixed_put (GTK_FIXED (fixed), endereco, 300, 50); gtk_widget_set_size_request (porta, 150, 40); gtk_fixed_put (GTK_FIXED (fixed), porta, 550, 50); gtk_widget_set_size_request (botaoAssocia, 100, 40); gtk_fixed_put (GTK_FIXED (fixed), botaoAssocia, 800, 50); gtk_widget_set_size_request (botaoAssociacao, 170, 40); gtk_fixed_put (GTK_FIXED (fixed), botaoAssociacao, 100, 420); gtk_widget_set_size_request (botaoPing, 170, 40); gtk_fixed_put (GTK_FIXED (fixed), botaoPing, 100, 470); gtk_widget_set_size_request (botaoAssociaHostCache, 170, 40); gtk_fixed_put (GTK_FIXED (fixed), botaoAssociaHostCache, 100, 520); gtk_widget_set_size_request (botaoImprimeHostCache, 170, 40); gtk_fixed_put (GTK_FIXED (fixed), botaoImprimeHostCache, 100, 570); gtk_fixed_put (GTK_FIXED (fixed), labelConsulta, 300, 110); gtk_widget_set_size_request (consulta, 400, 40); gtk_fixed_put (GTK_FIXED (fixed), consulta, 300, 130); gtk_widget_set_size_request (botaoConsulta, 100, 40); gtk_fixed_put (GTK_FIXED (fixed), botaoConsulta, 800, 130); gtk_widget_set_size_request (botaoDownload, 100, 40); gtk_fixed_put (GTK_FIXED (fixed), botaoDownload, 300, 200); gtk_widget_set_size_request (botaoLimpa, 100, 40); gtk_fixed_put (GTK_FIXED (fixed), botaoLimpa, 500, 200); gtk_widget_set_size_request (listaRemota, 600, 400); 154 Apêndice B. Código fonte do aplicativo Octopus gtk_fixed_put (GTK_FIXED (fixed), listaRemota, 300, 250); gtk_clist_set_column_width (GTK_CLIST (listaRemota), 0, 250); gtk_clist_set_column_width (GTK_CLIST (listaRemota), 1, 140); gtk_clist_set_column_width (GTK_CLIST (listaRemota), 2, 40); gtk_clist_set_column_width (GTK_CLIST (listaRemota), 2, 100); gtk_fixed_put (GTK_FIXED (fixed), logo, 70, 30); gtk_fixed_put (GTK_FIXED (fixed), letreiro, 50, 155); //Montando gtk_container_add (GTK_CONTAINER (janelaGeral), fixed); //Atribuido os sinais g_signal_connect (G_OBJECT (janelaGeral), "delete_event", G_CALLBACK (delete_event), NULL); g_signal_connect (G_OBJECT (botaoAssociacao), "clicked", G_CALLBACK (imprimeAssociacoesAbertas), NULL); g_signal_connect (G_OBJECT (botaoAssocia), "clicked", G_CALLBACK (associa), (gpointer) endereco); g_signal_connect (G_OBJECT (botaoConsulta), "clicked", G_CALLBACK (enviaConsulta), (gpointer) consulta); g_signal_connect (G_OBJECT (botaoDownload), "clicked", G_CALLBACK (enviaDownload), (gpointer) listaRemota); g_signal_connect (G_OBJECT (listaRemota), "select_row", GTK_SIGNAL_FUNC (selection_made), NULL); g_signal_connect (G_OBJECT (botaoLimpa), "clicked", G_CALLBACK (limpaLista), (gpointer) listaRemota); g_signal_connect (G_OBJECT (botaoPing), "clicked", G_CALLBACK (enviaPING), (gpointer) listaRemota); g_signal_connect (G_OBJECT (botaoImprimeHostCache), "clicked", G_CALLBACK (imprimeHostCache), NULL); g_signal_connect (G_OBJECT (botaoAssociaHostCache), "clicked", G_CALLBACK (associaHostCache), NULL); //Mostrando gtk_widget_show gtk_widget_show gtk_widget_show gtk_widget_show gtk_widget_show gtk_widget_show gtk_widget_show gtk_widget_show gtk_widget_show gtk_widget_show gtk_widget_show gtk_widget_show gtk_widget_show gtk_widget_show gtk_widget_show gtk_widget_show gtk_widget_show gtk_widget_show gtk_widget_show (labelEndereco); (labelPorta); (labelConsulta); (endereco); (porta); (consulta); (listaRemota); (botaoDownload); (botaoAssociacao); (botaoLimpa); (botaoPing); (botaoConsulta); (botaoAssocia); (botaoAssociaHostCache); (botaoImprimeHostCache); (logo); (letreiro); (fixed); (janelaGeral); gtk_main (); //Término dos gráficos return 0; 155 Apêndice B. Código fonte do aplicativo Octopus } //Fim de fazGraficos //----------------------------------------------------------------//Funcao que coloca uma figura na interface grafica //----------------------------------------------------------------GtkWidget * image_new_from_file_at_size (const char *filename, gint width, gint height) { GtkWidget *image = NULL; GdkPixbuf *pixbuf; GError *error; const gchar *st = "icon_size"; g_return_val_if_fail (filename != NULL, NULL); error = NULL; pixbuf = gdk_pixbuf_new_from_file (filename, &error); if (error != NULL) { g_warning (G_STRLOC ": cannot open %s: %s", filename, error->message); g_error_free (error); } if (pixbuf != NULL) { #if DEBUG debug (D_STATUS, "pixbuf new from file %s\n", filename); #endif GdkPixbuf *scaled = gdk_pixbuf_scale_simple (pixbuf, width, height, GDK_INTERP_BILINEAR); image = gtk_image_new (); gtk_image_set_from_pixbuf (GTK_IMAGE (image), scaled); g_object_unref (G_OBJECT (scaled)); g_object_unref (G_OBJECT (pixbuf)); } else { #if DEBUG debug (D_STATUS, "pixbuf new from file %s failed !\n", filename); #endif GtkIconSize icon_size = gtk_icon_size_register (st, width, height); image = gtk_image_new_from_stock (GTK_STOCK_MISSING_IMAGE, icon_size); } } return image; //Fim da funcao que insere uma figura na interface grafica //----------------------------------------------------------------//Fim de octopus.c //----------------------------------------------------------------- 156 Apêndice B. Código fonte do aplicativo Octopus /***************************************************************** SERVENTE.c - Nucleo do aplicativo Octopus Versao 1.0 Gustavo Salvadori Baptista do Carmo 27 de Fevereiro de 2006 [email protected] *****************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include <sys/types.h> <sys/socket.h> <netinet/in.h> <arpa/inet.h> <netinet/sctp.h> <errno.h> <netdb.h> <stdio.h> <stdlib.h> <unistd.h> <string.h> <pthread.h> <sys/dir.h> <sys/param.h> <fcntl.h> <signal.h> //----------------------------------------------------------------//Estruturas das mensagens Gnutella //----------------------------------------------------------------struct CABECALHO { unsigned int DescriptorID; unsigned char TTL; unsigned char Hops; }; struct PONG { short porta; int endereco; int numeroArquivos; float numeroKilobytes; }; struct QUERY { int DescriptorID; int TTL; int Hops; char consulta[100]; }; struct QUERYHIT { int DescriptorID; int TTL; int Hops; struct sockaddr_in endereco; long tamanhoArquivo; char nomeArquivo[100]; }; struct dadosPedido { struct sockaddr_in endereco; int fluxo; int sock_fd; char nomeArquivo[100]; int valorVetor; }; 157 Apêndice B. Código fonte do aplicativo Octopus int tamanhoCabecalho = sizeof (int) + sizeof (int) + sizeof (int); int tamanhoQUERYHIT = 3 * sizeof (int) + sizeof (struct sockaddr_in) + sizeof (long); //----------------------------------------------------------------//Variaveis e estruturas para a manipulacao do cache de associacoes //----------------------------------------------------------------int numeroDeAssociacoesAbertas = 0; struct cacheAssociacoes { int numeroSocket; struct sockaddr_in endereco; }; struct cacheAssociacoes associacoesAbertas[20]; //----------------------------------------------------------------//Variaveis e estruturas que implementam o cache de ID de mensagens //----------------------------------------------------------------int indiceListaLoop = 0; struct cacheIDMensagens { int ID; int NumeroSocket; struct sockaddr_in endereco; } listaDeIDs[200]; //----------------------------------------------------------------//Prototipos das funcoes locais //----------------------------------------------------------------//Manipulacao de associacoes void *abreServidor (struct sockaddr_in *servaddr); int associaPeer (struct sockaddr_in servaddr); //Manipulacao de mensagens void *manipuladorMensagens (int *); void enviaPING (void); void enviaPONG (struct sockaddr_in, struct CABECALHO, int); void enviaQUERY (char *); void enviaQUERYHIT (char nomeArquivo[], struct QUERY, struct sockaddr_in, int); void trataPING (struct CABECALHO mensagem, struct sockaddr_in cliaddr); void trataQUERY (struct QUERY, struct sockaddr_in, int); void trataQUERYHIT (struct QUERYHIT); void pedeArquivo (char *, char *, char *, char *); void *enviaArquivo (struct dadosPedido *); //Tratamento de loops e backtrack int trataLoop (int); void iniciaListaLoop (void); void incluiListaLoop (int, int, struct sockaddr_in); //Manipulacao do Host Cache int existeHostCache (struct sockaddr_in); int incluiHostCache (struct sockaddr_in); void imprimeHostCache (void); void associaHostCache (void); //Funcoes Miscelania void imprimeAssociacoesAbertas (void); int enderecoIgual (struct sockaddr_in, struct sockaddr_in); int existeEnderecoLista (struct sockaddr_in); int removeEnderecoLista (int); int aRespostaMinha (int); int retornaIndiceLista (int ID); int retornaIndiceListaEndereco (struct sockaddr_in); void adicionaResposta (char *texto[]); 158 Apêndice B. Código fonte do aplicativo Octopus int calculaTamanhoArquivo (char *); int contaArquivosDiretorio (void); int tamanhoDiretorio (void); void retornaNomeArquivoGET (char *, char *); void retornaSeguinte (char *, int, char *); int existeSocketLista (int); //Variaveis Miscelania int socketNovoParaAssociacoes[20]; int indiceSocketNovoAssociacoes = 0; pthread_t threadCliente[20]; FILE *hostCacheFile; //Variaveis do octopus.c extern struct sockaddr_in meuaddr; extern GtkWidget *listaRemota; extern gint linhaListaLocal; //----------------------------------------------------------------//Funcao para enviar o arquivo para alguem que pediu //Envia informacoes sobre o arquivo, em seguida os dados //----------------------------------------------------------------void * enviaArquivo (struct dadosPedido *buffer) { int quantidadeEnviada; int out_sz; int o = 0; char *stringPedido; char bufferArquivo[200]; char nomeArquivoCompleto[100]; struct dadosPedido dados; FILE *arquivo; //Preenchendo estrutura local - informacoes da transferencia dados.endereco = buffer->endereco; strcpy (dados.nomeArquivo, buffer->nomeArquivo); dados.sock_fd = buffer->sock_fd; dados.fluxo = buffer->fluxo; dados.valorVetor = buffer->valorVetor; printf printf printf printf printf printf ("\nEstou prestes a enviar um arquivo\n"); ("Nome do arquivo:%s\n", dados.nomeArquivo); ("Numero socket:%d\n", dados.sock_fd); ("Pedido por:%s\n", inet_ntoa (dados.endereco.sin_addr)); ("Porta:%d\n", ntohs (dados.endereco.sin_port)); ("Fluxo de envio:%d\n\n", dados.fluxo); bzero (nomeArquivoCompleto, 100); strcat (nomeArquivoCompleto, "./ArquivosCompartilhados/"); strcat (nomeArquivoCompleto, dados.nomeArquivo); stringPedido = malloc (100); bzero (stringPedido, 100); arquivo = fopen (nomeArquivoCompleto, "rb"); //Envia mensagem de erro se der problema ao abrir o arquivo if (arquivo == NULL) { printf ("Problema ao abrir o arquivo\n"); sprintf (stringPedido, "OCTOPUS/1.0 003 ERRO\r\n/Arquivo:%s\r\nMotivo:Arquivo nao encontrado\r\n\r\n", dados.nomeArquivo); printf ("Enviando mensagem de erro:%s\n", stringPedido); out_sz = strlen (stringPedido); 159 Apêndice B. Código fonte do aplicativo Octopus quantidadeEnviada = sctp_sendmsg (dados.sock_fd, stringPedido, out_sz, (struct sockaddr *) &dados.endereco, sizeof (struct sockaddr), 0, MSG_UNORDERED, 5, 0, 0); if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar a mensagem\n"); free (stringPedido); memcpy ((void *) dados.valorVetor, &o, sizeof (int)); pthread_exit (0); } free (stringPedido); memcpy ((void *) dados.valorVetor, &o, sizeof (int)); pthread_exit (0); } //Fim de erro de arquivo //Mensagem de confirmacao que esta tudo ok sprintf (stringPedido, "OCTOPUS/1.0 001 OK\r\n/Arquivo:%s\r\n/Tamanho:%d/\r\n\r\n", dados.nomeArquivo,calculaTamanhoArquivo (dados.nomeArquivo)); printf ("Mensagem sobre as informacoes:%s\n", stringPedido); out_sz = strlen (stringPedido); quantidadeEnviada = sctp_sendmsg (dados.sock_fd, stringPedido, out_sz, (struct sockaddr *) &dados.endereco, sizeof (struct sockaddr), 0, 0, dados.fluxo, 0, 0); if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar a mensagem\n"); free (stringPedido); pthread_exit (0); } free (stringPedido); //Tudo ok, enviando o arquivo para o cliente de arquivos while (!(feof (arquivo))) { bzero (bufferArquivo, 200); out_sz = fread (bufferArquivo, sizeof (char), 200, arquivo); quantidadeEnviada = sctp_sendmsg (dados.sock_fd, &bufferArquivo, out_sz, (struct sockaddr *) &dados.endereco, sizeof (struct sockaddr), 0, 0, dados.fluxo, 0, 0); if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar a mensagem\n"); free (bufferArquivo); fclose (arquivo); pthread_exit (0); } } //End while de envio do arquivo fclose (arquivo); //Liberando o fluxo que foi utilizado para enviar o arquivo memcpy ((void *) dados.valorVetor, &o, sizeof (int)); pthread_exit (0); 160 Apêndice B. Código fonte do aplicativo Octopus } //Fim enviaArquivo //----------------------------------------------------------------//Funcao para enviar os dados de solicitacao //do arquivo a ser transferido //----------------------------------------------------------------void pedeArquivo (char *nomeArquivo, char *endereco, char *porta, char *tamanho) { int numeroSocket; int quantidadeEnviada; int out_sz; char *stringPedido; struct sockaddr_in enderecoPedido; stringPedido = malloc (100); bzero (stringPedido, 100); sprintf (stringPedido, "GET /get/%s/ OCTOPUS/1.0\r\n\r\n", nomeArquivo); printf ("A string de pedido eh:%s\n", stringPedido); //Configurando o endereco para quem vai pedir bzero (&enderecoPedido, sizeof (enderecoPedido)); enderecoPedido.sin_family = AF_INET; inet_aton (endereco, &enderecoPedido.sin_addr); enderecoPedido.sin_port = htons (atoi (porta)); printf printf printf printf printf ("\nEstou solicitando um arquivo\n"); ("Nome do arquivo:%s\n", nomeArquivo); ("Endereco de contato:%s\n", inet_ntoa (enderecoPedido.sin_addr)); ("Porta de contato:%d\n\n", ntohs (enderecoPedido.sin_port)); ("Tamanho do arquivo:%s\n", tamanho); //Tratar aqui a associacao com um no que jah estou associado if (existeEnderecoLista (enderecoPedido) == 0) { associaPeer (enderecoPedido); printf ("Me associei ao no que estava longe para transferir arquivo\n"); } printf ("Jah estou associado\n"); numeroSocket = associacoesAbertas[retornaIndiceListaEndereco (enderecoPedido)].numeroSocket; printf ("Numero do socket local do contato:%d\n", numeroSocket); out_sz = strlen (stringPedido); quantidadeEnviada = sctp_sendmsg (numeroSocket, stringPedido, out_sz, (struct sockaddr *) &enderecoPedido, sizeof (struct sockaddr), 0, MSG_UNORDERED, 5, 0, 0); if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar a mensagem de pedido de arquivo\n"); } printf ("Quantidade enviada para pedido de arquivo:%d\n", quantidadeEnviada); free (stringPedido); return; } //fim de pedeArquivo 161 Apêndice B. Código fonte do aplicativo Octopus //----------------------------------------------------------------//- Tratamento de mensagens //As mensagens que chegam ao no sao recebidas por esta funcao //----------------------------------------------------------------void * manipuladorMensagens (int *socketNumber) { int sock_fd = *socketNumber; socklen_t len; int msg_flags; char *readbuf; char *mensagemErro; char nomeArquivo[100]; int out_sz; int quantidadeRecebida; int quantidadeEnviada; int quantidadeEscrita; struct sockaddr_in cliaddr; struct sctp_sndrcvinfo sri; len = sizeof (struct sockaddr_in); readbuf = malloc (200); mensagemErro = malloc (150); printf ("\nO manipulador de mensagens foi aberto com o socket numero: %d\n", sock_fd); struct struct struct struct struct PONG dadosPong; CABECALHO ping; QUERY corpo; QUERYHIT resposta; sockaddr_in END; FILE *arquivo1; FILE *arquivo2; FILE *arquivo3; pthread_t thread_servidor1; pthread_t thread_servidor2; pthread_t thread_servidor3; int vetorEstadosTransferencia[3] = { 0, 0, 0 }; int vetorEstadosRecebimento[3] = { 0, 0, 0 }; int fluxo; struct dadosPedido bufferEnvio; //Aqui e o loop de direcionamento do tratamento das mensagens while (1) { bzero (&sri, sizeof (sri)); bzero (readbuf, 200); quantidadeRecebida = sctp_recvmsg (sock_fd, readbuf, 200, (struct sockaddr *) &cliaddr, &len, &sri, &msg_flags); if (quantidadeRecebida == (-1)) { printf ("Falha ao receber a mensagem - encerrando thread\n"); removeEnderecoLista (sock_fd); if (vetorEstadosRecebimento[0] != 0) fclose (arquivo1); if (vetorEstadosRecebimento[1] != 0) fclose (arquivo2); if (vetorEstadosRecebimento[2] != 0) fclose (arquivo3); free (readbuf); pthread_exit (0); } 162 Apêndice B. Código fonte do aplicativo Octopus //Tratamento das mensagens PING if ((sri.sinfo_stream) == 1) { bzero (&ping, sizeof (struct CABECALHO)); memcpy (&ping, readbuf, quantidadeRecebida); //Se nao houve loop if (trataLoop (ping.DescriptorID) == 0) { printf ("\nRecebi um PING\n"); printf ("Descriptor ID:%d\n", ping.DescriptorID); printf ("TTL=%d\n", ping.TTL); printf ("Hops=%d\n", ping.Hops); printf ("Recebi do endereco:%s\n", inet_ntoa (cliaddr.sin_addr)); incluiListaLoop (ping.DescriptorID, sock_fd, cliaddr); enviaPONG (cliaddr, ping, sock_fd); trataPING (ping, cliaddr); } else continue; } //senao joga fora o descritor //fim de PING //Tratamento das mensagens PONG if ((sri.sinfo_stream) == 2) { bzero (&ping, sizeof (struct CABECALHO)); memcpy (&ping, readbuf, sizeof (struct CABECALHO)); bzero (&dadosPong, sizeof (dadosPong)); memcpy (&dadosPong, (readbuf + sizeof (struct CABECALHO)), sizeof (dadosPong)); bzero (&END, sizeof (struct sockaddr_in)); memcpy (&END.sin_addr, &dadosPong.endereco, sizeof (int)); END.sin_port = htons (dadosPong.porta); //Atualizando host cache incluiHostCache (END); printf printf printf printf ("\nDados do PONG recebido\n"); ("Quantidade:%d\n", quantidadeRecebida); ("ID do descritor:%d\n", ping.DescriptorID); ("Endereco IP do servente:%s\n", inet_ntoa (END.sin_addr)); printf ("Porta do servente:%d\n", dadosPong.porta); printf ("Quantidade de arquivos:%d\n", dadosPong.numeroArquivos); printf ("Quantidade de dados compartilhados:%f\n", dadosPong.numeroKilobytes); } //fim de PONG //Tratamento das mensagens QUERY if ((sri.sinfo_stream) == 3) { bzero (&corpo, sizeof (struct QUERY)); memcpy (&corpo, readbuf, quantidadeRecebida); //Se nao houve loop if (trataLoop (corpo.DescriptorID) == 0) { incluiListaLoop (corpo.DescriptorID, sock_fd, cliaddr); trataQUERY (corpo, cliaddr, sock_fd); } 163 Apêndice B. Código fonte do aplicativo Octopus else continue; } //senao joga fora o descritor //fim de QUERY //Tratamento das mensagens QUERYHIT if ((sri.sinfo_stream) == 4) { bzero (&resposta, sizeof (resposta)); memcpy (&resposta, readbuf, quantidadeRecebida); trataQUERYHIT (resposta); } //fim de QUERYHIT //Tratamento da negociacao da transferencia de arquivo if ((sri.sinfo_stream) == 5) { //Recebi um pedido if (strstr (readbuf, "GET /get/") != NULL) { printf ("O outro no estah pedindo um arquivo\n"); bzero (&bufferEnvio, sizeof (struct dadosPedido)); retornaNomeArquivoGET (readbuf, bufferEnvio.nomeArquivo); bufferEnvio.endereco = cliaddr; bufferEnvio.sock_fd = sock_fd; if (vetorEstadosTransferencia[0] == 0) { fluxo = 6; vetorEstadosTransferencia[0] = 1; bufferEnvio.fluxo = fluxo; bufferEnvio.valorVetor = &vetorEstadosTransferencia[0]; pthread_create (&thread_servidor1, NULL, (void *) enviaArquivo, &bufferEnvio); pthread_detach (thread_servidor1); } else if (vetorEstadosTransferencia[1] == 0) { fluxo = 7; vetorEstadosTransferencia[1] = 1; bufferEnvio.fluxo = fluxo; bufferEnvio.valorVetor = &vetorEstadosTransferencia[1]; pthread_create (&thread_servidor2, NULL, (void *) enviaArquivo, &bufferEnvio); pthread_detach (thread_servidor2); } else if (vetorEstadosTransferencia[2] == 0) { fluxo = 8; vetorEstadosTransferencia[2] = 1; bufferEnvio.fluxo = fluxo; bufferEnvio.valorVetor = &vetorEstadosTransferencia[2]; pthread_create (&thread_servidor3, NULL, (void *) enviaArquivo, &bufferEnvio); pthread_detach (thread_servidor3); } else { //Envia mensagem de erro - fluxos insuficientes retornaNomeArquivoGET (readbuf, nomeArquivo); bzero (mensagemErro, 150); sprintf (mensagemErro, 164 Apêndice B. Código fonte do aplicativo Octopus "OCTOPUS/1.0 002 ERRO\r\n/Arquivo:%s\r\n/Tamanho:%d/\r\n Motivo:Todos os fluxos ocupados\r\n\r\n", nomeArquivo, calculaTamanhoArquivo (nomeArquivo)); printf ("Enviando mensagem de erro:%s\n", mensagemErro); out_sz = strlen (mensagemErro); quantidadeEnviada = sctp_sendmsg (sock_fd, mensagemErro, out_sz, (struct sockaddr *) &cliaddr, sizeof (struct sockaddr), 0, MSG_UNORDERED, 5, 0, 0); if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar a mensagem de erro 002\n"); } printf ("Quantidade enviada como erro:%d\n", quantidadeEnviada); } //Fim de envio de mensagem de erro } //Fim de IF GET /get/ else if ((strstr (readbuf, "OCTOPUS/1.0 002 ERRO") != NULL) || (strstr (readbuf, "OCTOPUS/1.0 003 ERRO") != NULL)) { printf ("\n%s", readbuf); continue; } //Fim do recebimento de erros } //Fim de tratamento de transferencia de arquivos //Tratamento dos fluxos usados para transferir arquivos if ((sri.sinfo_stream) > 5) { //O outro no vai enviar o arquivo //Enviou mensagem de OK if (strstr (readbuf, "OCTOPUS/1.0 001 OK") != NULL) { char *nomeArquivo = malloc (100); char *tamanhoArquivo = malloc (20); retornaSeguinte (readbuf, 1, nomeArquivo); retornaSeguinte (readbuf, 2, tamanhoArquivo); printf ("Nome do arquivo:%s\n", nomeArquivo); printf ("Tamanho do arquivo:%s\n", tamanhoArquivo); char nomeArquivoCompleto[100]; bzero (nomeArquivoCompleto, 100); strcat (nomeArquivoCompleto, "./ArquivosCompartilhados/"); strcat (nomeArquivoCompleto, nomeArquivo); strcat (nomeArquivoCompleto, ".novo"); if (sri.sinfo_stream == 6) { arquivo1 = fopen (nomeArquivoCompleto, "wb"); vetorEstadosRecebimento[0] = atoi (tamanhoArquivo); } if (arquivo1 == NULL) { printf ("Deu problema ao abrir o arquivo\n"); vetorEstadosRecebimento[0] = 0; 165 Apêndice B. Código fonte do aplicativo Octopus } if (sri.sinfo_stream == 7) { arquivo2 = fopen (nomeArquivoCompleto, "wb"); vetorEstadosRecebimento[1] = atoi (tamanhoArquivo); } if (arquivo2 == NULL) { printf ("Deu problema ao abrir o arquivo\n"); vetorEstadosRecebimento[1] = 0; } if (sri.sinfo_stream == 8) { arquivo3 = fopen (nomeArquivoCompleto, "wb"); vetorEstadosRecebimento[2] = atoi (tamanhoArquivo); } if (arquivo3 == NULL) { printf ("Deu problema ao abrir o arquivo\n"); vetorEstadosRecebimento[2] = 0; } printf ("Arquivo %s aberto\n", nomeArquivoCompleto); free (nomeArquivo); free (tamanhoArquivo); } //fim de enviou mensagem de OK else //Escrevendo os dados no arquivo { printf ("Escrevi\n"); printf ("Quantidade de bytes que veio:%d\n", quantidadeRecebida); printf ("Fluxo:%d\n", sri.sinfo_stream); if (sri.sinfo_stream == 6) { quantidadeEscrita = fwrite (readbuf, sizeof (char), quantidadeRecebida, arquivo1); vetorEstadosRecebimento[0] -= quantidadeEscrita; if (vetorEstadosRecebimento[0] == 0) fclose (arquivo1); } if (sri.sinfo_stream == 7) { quantidadeEscrita = fwrite (readbuf, sizeof (char), quantidadeRecebida, arquivo2); vetorEstadosRecebimento[1] -= quantidadeEscrita; if (vetorEstadosRecebimento[1] == 0) fclose (arquivo2); } } } if (sri.sinfo_stream == 8) { quantidadeEscrita = fwrite (readbuf, sizeof (char), quantidadeRecebida, arquivo3); vetorEstadosRecebimento[2] -= quantidadeEscrita; if (vetorEstadosRecebimento[2] == 0) fclose (arquivo3); } } //Fim de escrevendo os dados no arquivo //Fim de tratamento de fluxos //fim do while(1) 166 Apêndice B. Código fonte do aplicativo Octopus return 0; } //fim do manipulador de mensagens //----------------------------------------------------------------//Funcao que abre o servidor que aguarda novas associacoes //----------------------------------------------------------------void * abreServidor (struct sockaddr_in *serv) { int sock_fd; int erro; struct struct struct struct sockaddr_in servaddr; sockaddr_in cliaddr; sctp_sndrcvinfo sri; sctp_event_subscribe evnts; bzero (&servaddr, sizeof (servaddr)); servaddr = *serv; printf ("\nAbrindo o Servidor\n"); printf ("No endereco:%s\n", inet_ntoa (servaddr.sin_addr)); printf ("Na porta:%d\n\n", ntohs (servaddr.sin_port)); //zerando o cache de ID de associacoes iniciaListaLoop (); //Cria o socket SCTP sock_fd = socket (AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP); if (sock_fd < 0) { printf ("Criacao do socket falhou\n"); exit (0); } //Inscrevendo nos eventos que devem ser notificados bzero (&evnts, sizeof (evnts)); evnts.sctp_data_io_event = 1; erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &evnts, sizeof (evnts)); if (erro != 0) { printf ("Erro ao setar eventos\n"); exit (0); } //Definindo o tempo maximo de idle time de uma associacao para encerramento automatico int close_time = 240; erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_AUTOCLOSE, &close_time, sizeof (close_time)); if (erro != 0) { printf ("Erro ao setar a opcao de close time\n"); exit (0); } //Definindo o numero de fluxos que entram e saem, assim como timeouts struct sctp_initmsg initmsg; initmsg.sinit_num_ostreams = 10; initmsg.sinit_max_instreams = 10; initmsg.sinit_max_attempts = 3; initmsg.sinit_max_init_timeo = 30; erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_INITMSG, (char *) &initmsg, sizeof (struct sctp_initmsg)); if (erro != 0) { 167 Apêndice B. Código fonte do aplicativo Octopus printf ("Erro ao setar a opcao de initmessage\n"); exit (0); } //Binding erro = bind (sock_fd, (struct sockaddr *) &servaddr, sizeof (servaddr)); if (erro != 0) { printf ("Erro ao dar o bind\n"); exit (0); } erro = listen (sock_fd, 10); if (erro != 0) { printf ("Erro ao setar a entrar no listen\n"); exit (0); } socklen_t len; pthread_t thread_servidor[20]; sctp_assoc_t IDassociacao; int quantidadeEnviada; int quantidadeRecebida; int msg_flags; int i = 0; int SocketNovo[10]; char *readbuf; len = sizeof (struct sockaddr_in); readbuf = malloc (105); while (1) { bzero (readbuf, 105); bzero (&cliaddr, sizeof (cliaddr)); //recebendo a mensagem do cliente //A partir daqui se estabelece uma associacao quantidadeRecebida = sctp_recvmsg (sock_fd, readbuf, 105, (struct sockaddr *) &cliaddr, &len, &sri, &msg_flags); if (quantidadeRecebida == (-1)) { printf ("Falha ao receber a mensagem\n"); continue; } //Dados para conferencia printf ("Informacoes sobre o no requerente:\n"); printf ("Socket:%d\n", sock_fd); printf ("Numero da stream:%d\n", sri.sinfo_stream); printf ("Numero de bytes recebidos:%d\n", quantidadeRecebida); printf ("Endereco remoto:%s\n", inet_ntoa (cliaddr.sin_addr)); printf ("Porta remota:%d\n", ntohs (cliaddr.sin_port)); printf ("Mensagem recebida:%s\n", readbuf); //nao ha mais slots de associacao disponiveis if (i == 21) { bzero (readbuf, 105); sprintf (readbuf, "500 ERROR - NO MORE AVAILABLE SLOTS\n\n"); quantidadeEnviada = sctp_sendmsg (sock_fd, readbuf, quantidadeRecebida, (struct sockaddr *) &cliaddr, sizeof (struct sockaddr), sri.sinfo_ppid, sri.sinfo_flags, 168 Apêndice B. Código fonte do aplicativo Octopus sri.sinfo_stream, 0, 0); //enviando devolta a mensagem if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar a mensagem\n"); } } continue; //fim de tratamento de casos de fim de slots disponiveis //efetivando a associacao com outro no - handshake Gnutella if (strcmp (readbuf, "GNUTELLA CONNECT/0.4\n\n") == 0) { printf ("GNUTELLA CONNECT/0.4 - Aceito\n"); //Sim, houve, envia a proxima leg bzero (readbuf, 105); sprintf (readbuf, "GNUTELLA OK\n\n"); quantidadeEnviada = sctp_sendmsg (sock_fd, readbuf, quantidadeRecebida, (struct sockaddr *) &cliaddr, sizeof (struct sockaddr), sri.sinfo_ppid, sri.sinfo_flags, sri.sinfo_stream, 0, 0); //enviando devolta a mensagem if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar a mensagem\n"); continue; } printf ("\nEnviado o GNUTELLA OK\n"); printf ("Quantidade enviada:%d\n\n", quantidadeEnviada); IDassociacao = sri.sinfo_assoc_id; SocketNovo[i] = sctp_peeloff (sock_fd, IDassociacao); if (SocketNovo[i] == (-1)) { printf ("Falha ao receber no peeloff\n"); continue; } printf ("SOCKET NOVO:%d \n", SocketNovo[i]); //Atualizando o cache de associacoes com a associacao vindoura numeroDeAssociacoesAbertas++; associacoesAbertas[numeroDeAssociacoesAbertas]. numeroSocket = SocketNovo[i]; associacoesAbertas[numeroDeAssociacoesAbertas].endereco = cliaddr; printf ("Associacao vindoura ok, e inclusa no cache de associacoes\n\n"); pthread_create (&thread_servidor[i], NULL, (void *) manipuladorMensagens, &SocketNovo[i]); if (pthread_detach (thread_servidor[i]) == 0) printf ("Detach da thread ok\n"); i++; //Atualiza numero suportavel de slots de associacao } } //Fim do if do GNUTELLA CONNECT //fim do while 1 free (readbuf); pthread_exit (0); } //Fim de abreServidor //----------------------------------------------------------------//Funcao para fazer um pedido de associacao a outro no //Retorna 1 em sucesso e 0 em falha 169 Apêndice B. Código fonte do aplicativo Octopus //----------------------------------------------------------------int associaPeer (struct sockaddr_in servaddr) { printf ("\nTentativa de associar peer\n"); printf ("O endereco a associar:%s\n", inet_ntoa (servaddr.sin_addr)); printf ("A porta a associar:%d\n\n", ntohs (servaddr.sin_port)); int sock_fd; int erro; struct sockaddr_in enderecoEnviante; struct sctp_event_subscribe evnts; if (existeEnderecoLista (servaddr) == 1) { printf ("Voce ja possui uma associacao com esse no\n\n"); return 0; } if (numeroDeAssociacoesAbertas == 20) { printf ("Nao e possivel abrir mais associacoes\n"); return 0; } //Criando o socket sock_fd = socket (AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP); if (sock_fd < 0) { printf ("Criacao do socket falhou\n"); return 0; } //Inscricao de eventos para receber notificacoes bzero (&evnts, sizeof (evnts)); evnts.sctp_data_io_event = 1; erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &evnts, sizeof (evnts)); if (erro != 0) { printf ("Erro a setar opcao do socket de initmessage\n"); return 0; } //Definindo numero de fluxos a utilizar struct sctp_initmsg initmsg; initmsg.sinit_num_ostreams = 10; initmsg.sinit_max_instreams = 10; initmsg.sinit_max_attempts = 3; initmsg.sinit_max_init_timeo = 30; erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_INITMSG, (char *) &initmsg, sizeof (struct sctp_initmsg)); if (erro != 0) { printf ("Erro a setar opcao do socket de initmessage\n"); return 0; } //Definindo close time automatico int close_time = 240; erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_AUTOCLOSE, &close_time, sizeof (close_time)); if (erro != 0) { printf ("Erro ao setar a opcao de close time\n"); return 0; 170 Apêndice B. Código fonte do aplicativo Octopus } struct sctp_sndrcvinfo sri; int quantidadeEnviada; int quantidadeRecebida; int msg_flags; int out_sz; char *sendline; socklen_t len; len = sizeof (struct sockaddr_in); sendline = malloc (50); //Definindo o fluxo zero para envio de mensagens Gnutella Connect bzero (&sri, sizeof (sri)); sri.sinfo_stream = 0; bzero (sendline, sizeof (50)); sprintf (sendline, "GNUTELLA CONNECT/0.4\n\n"); out_sz = strlen (sendline); printf ("Enviando o GNUTELLA CONNECT/0.4\n"); quantidadeEnviada = sctp_sendmsg (sock_fd, sendline, out_sz, (struct sockaddr *) &servaddr, sizeof (struct sockaddr), 0, 0, sri.sinfo_stream, 0, 0); if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar a mensagem de GNUTELLA CONNECT\n"); return 0; } bzero (sendline, sizeof (50)); quantidadeRecebida = sctp_recvmsg (sock_fd, sendline, 50, (struct sockaddr *) &enderecoEnviante, &len, &sri, &msg_flags); if (quantidadeRecebida == (-1)) { printf ("Falha ao receber a mensagem de GNUTELLA OK\n"); return 0; } //aqui eu vou preencher a lista de descritores de sockets if (strcmp (sendline, "GNUTELLA OK\n\n") == 0) { numeroDeAssociacoesAbertas++; associacoesAbertas[numeroDeAssociacoesAbertas].numeroSocket = sock_fd; associacoesAbertas[numeroDeAssociacoesAbertas].endereco = servaddr; printf ("Recebi o %s\n\n", sendline); printf ("Entrando na thread com o socket de numero %d\n",sock_fd); socketNovoParaAssociacoes[indiceSocketNovoAssociacoes] = sock_fd; pthread_create (&threadCliente[indiceSocketNovoAssociacoes], NULL, (void *) manipuladorMensagens, &socketNovoParaAssociacoes [indiceSocketNovoAssociacoes]); if (pthread_detach(threadCliente[indiceSocketNovoAssociacoes]) == 0) printf ("Detach da thread ok\n"); indiceSocketNovoAssociacoes++; free (sendline); return 1; } 171 Apêndice B. Código fonte do aplicativo Octopus else { close (sock_fd); printf ("Associacao nao foi aceita\n"); free (sendline); return 0; } } //fim de associaPeer //----------------------------------------------------------------//Tratamento das mensangens PING //----------------------------------------------------------------void trataPING (struct CABECALHO mensagem, struct sockaddr_in cliaddr) { printf ("\nEstou tratando um PING\n"); //Esse trecho eh para propagar os PINGs para outros nos int out_sz; int quantidadeEnviada; struct sockaddr_in servaddr; int i; out_sz = sizeof (mensagem); //Descarta o pacote se o TTL esgotou if ((mensagem.TTL) == 0) return; else { (mensagem.TTL)--; (mensagem.Hops)++; } //para cada associacao que foi feita, envia um QUERY, //menos para aquela que enviou for (i = 1; i <= numeroDeAssociacoesAbertas; i++) { servaddr = associacoesAbertas[i].endereco; if (enderecoIgual (servaddr, cliaddr) != 1) { quantidadeEnviada = sctp_sendmsg (associacoesAbertas[i].numeroSocket, &mensagem, out_sz, (struct sockaddr *) &servaddr, sizeof (struct sockaddr), 0, MSG_UNORDERED, 1, 0, 0); if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar a mensagem\n"); } } } printf ("Quantidade de bytes de PING enviada:%d\n", quantidadeEnviada); printf ("Para o endereco:%s\n", inet_ntoa (servaddr.sin_addr)); } //fim do If //fim do for de propagacao de PINGs //Fim trataPING //----------------------------------------------------------------//Funcao para o envio de PINGs aos nos diretamente associados //----------------------------------------------------------------void enviaPING (void) 172 Apêndice B. Código fonte do aplicativo Octopus { int out_sz; int i; int quantidadeEnviada; struct sockaddr_in servaddr; struct CABECALHO mensagem; //Nao pode fazer enviar PING se nao esta associado a ninguem if (numeroDeAssociacoesAbertas == 0) { printf ("Nenhuma associacao efetivada\n"); return; } //Preenchendo o payload //gerando um numero randomico para o identificador da mensagem bzero (&mensagem, sizeof (mensagem)); srand (time (0)); mensagem.DescriptorID = rand (); mensagem.TTL = 0; mensagem.Hops = 0; printf printf printf printf ("\nEnviando PING\n"); ("DescriptorID:%d\n", mensagem.DescriptorID); ("TTL:%d\n", mensagem.TTL); ("Hops:%d\n", mensagem.Hops); //quantidade de dados que vai enviar atraves do socket out_sz = sizeof (mensagem); //para cada associacao aberta, envia um PING for (i = 1; i <= numeroDeAssociacoesAbertas; i++) { servaddr = associacoesAbertas[i].endereco; quantidadeEnviada = sctp_sendmsg (associacoesAbertas[i].numeroSocket, &mensagem, out_sz, (struct sockaddr *) &servaddr, sizeof (struct sockaddr), 0, MSG_UNORDERED, 1, 0, 0); if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar PING\n"); } printf ("Quantidade Enviada:%d\n", quantidadeEnviada); printf ("Para o endereco:%s\n", inet_ntoa (servaddr.sin_addr)); //Para identificacao de quando a mensagem voltar, para exibir os resultados incluiListaLoop (mensagem.DescriptorID, 0, meuaddr); } } //fim do for de envio de mensagens PING //Fim do enviaPING //----------------------------------------------------------------//Envia uma mensagem de PONG em resposta ao PING //----------------------------------------------------------------void enviaPONG (struct sockaddr_in enderecoCliente, struct CABECALHO cabeca, int sock_fd) { int quantidadeEnviada; int out_sz; struct PONG resposta; struct CABECALHO mensagemResp; char *buffer; buffer = malloc (200); 173 Apêndice B. Código fonte do aplicativo Octopus bzero (buffer, 200); //Setar os dados do cabecalho de resposta bzero (&mensagemResp, sizeof (mensagemResp)); mensagemResp.DescriptorID = cabeca.DescriptorID; mensagemResp.TTL = cabeca.Hops + 1; mensagemResp.Hops = 0; //Setar os dados da resposta bzero (&resposta, sizeof (struct PONG)); resposta.porta = (short) ntohs (meuaddr.sin_port); memcpy (&resposta.endereco, &meuaddr.sin_addr, sizeof (int)); resposta.numeroArquivos = contaArquivosDiretorio (); resposta.numeroKilobytes = (float) (tamanhoDiretorio () / 1000); //Passando pro buffer o cabecalho + resposta memcpy (buffer, &mensagemResp, sizeof (struct CABECALHO)); memcpy (buffer + sizeof (struct CABECALHO), &resposta, sizeof (struct PONG)); printf printf printf printf printf printf ("\nEnviando PONG\n"); ("O ID do descritor:%d\n", mensagemResp.DescriptorID); ("O TTL:%d\n", mensagemResp.TTL); ("O Hop:%d\n", mensagemResp.Hops); ("A quantidade de arquivos:%d\n", resposta.numeroArquivos); ("Tamanho do diretorio:%f\n", resposta.numeroKilobytes); out_sz = sizeof (struct CABECALHO) + sizeof (struct PONG); //Enviar os dados da mensagem PONG quantidadeEnviada = sctp_sendmsg (sock_fd, buffer, out_sz, (struct sockaddr *) &enderecoCliente, sizeof (struct sockaddr), 0, MSG_UNORDERED, 2, 0, 0); if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar a mensagem\n"); } printf ("Quantidade enviada como PONG:%d\n", quantidadeEnviada); //Enviando os PONGs com os enderecos do host cache struct sockaddr_in endereco; hostCacheFile = fopen ("./hostCacheFile.dat", "rb"); if (hostCacheFile == NULL) { printf ("Erro ao abrir o arquivo do Host Cache\n\n"); free (buffer); return; } while (!feof (hostCacheFile)) { bzero (&endereco, sizeof (endereco)); fread (&endereco, sizeof (endereco), 1, hostCacheFile); if (strcmp (inet_ntoa (endereco.sin_addr), "0.0.0.0") != 0) { //Setar os dados da resposta bzero (&resposta, sizeof (struct PONG)); resposta.porta = (short) ntohs (endereco.sin_port); memcpy (&resposta.endereco, &endereco.sin_addr, sizeof (int)); resposta.numeroArquivos = 0; resposta.numeroKilobytes = 0; //Passando pro buffer cabecalho+resposta memcpy (buffer + sizeof (struct CABECALHO), &resposta, 174 Apêndice B. Código fonte do aplicativo Octopus sizeof (struct PONG)); printf printf printf printf printf printf printf printf ("\nEnviando PONG\n"); ("O ID do descritor:%d\n", mensagemResp.DescriptorID); ("O TTL:%d\n", mensagemResp.TTL); ("O Hop:%d\n", mensagemResp.Hops); ("A quantidade de arquivos:%d\n", resposta.numeroArquivos); ("Tamanho do diretorio:%f\n", resposta.numeroKilobytes); ("Endereco:%s\n", inet_ntoa (endereco.sin_addr)); ("Porta:%d\n\n", ntohs (endereco.sin_port)); quantidadeEnviada = sctp_sendmsg (sock_fd, buffer, out_sz, (struct sockaddr *) &enderecoCliente, sizeof (struct sockaddr), 0, MSG_UNORDERED, 2, 0, 0); if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar a mensagem\n"); } } printf ("Quantidade enviada como PONG:%d\n", quantidadeEnviada); } //Fim do if //fim do While(!feof) fclose (hostCacheFile); free (buffer); return; } //Fim de enviaPONG //----------------------------------------------------------------//Envia a QUERY solicitada pelo usuario //----------------------------------------------------------------void enviaQUERY (char *consulta) { int out_sz; int i; int quantidadeEnviada; struct sockaddr_in servaddr; struct QUERY cabeca; //Nao pode fazer consulta se nao esta associado a ninguem if (numeroDeAssociacoesAbertas == 0) { printf ("Nenhuma associacao efetivada\n"); return; } //Preenchendo o payload //Gerando um numero randomico para o identificador do descritor bzero (&cabeca, sizeof (cabeca)); srand (time (0)); cabeca.DescriptorID = rand (); cabeca.TTL = 4; cabeca.Hops = 0; strcpy (cabeca.consulta, consulta); printf printf printf printf printf printf ("Enviando QUERY do usuario\n"); ("DescriptorID:%d\n", cabeca.DescriptorID); ("TTL:%d\n", cabeca.TTL); ("Hops:%d\n", cabeca.Hops); ("Consulta da cabeca %s\n\n", cabeca.consulta); ("Consulta sem cabeca %s\n\n", consulta); //quantidade de dados que vai enviar atraves do socket out_sz = tamanhoCabecalho + strlen (consulta); 175 Apêndice B. Código fonte do aplicativo Octopus //para cada associacao que foi feita, envia uma QUERY for (i = 1; i <= numeroDeAssociacoesAbertas; i++) { servaddr = associacoesAbertas[i].endereco; quantidadeEnviada = sctp_sendmsg (associacoesAbertas[i].numeroSocket, &cabeca, out_sz, (struct sockaddr *) &servaddr, sizeof (struct sockaddr), 0, MSG_UNORDERED, 3, 0, 0); if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar a mensagem\n"); } printf ("Quantidade Enviada:%d\n", quantidadeEnviada); printf ("Para o endereco:%s\n", inet_ntoa (servaddr.sin_addr)); //Para identificacao se um QUERYHIT voltar //Inclui no cache de ID de mensagens incluiListaLoop (cabeca.DescriptorID, 0, meuaddr); } } //fim do for de envio de mensagens QUERY //Fim do enviaQUERY //----------------------------------------------------------------//Quando satisfaz a consulta, envia um QUERYHIT //Inicio de backtrack //----------------------------------------------------------------void enviaQUERYHIT (char nomeArquivo[], struct QUERY corpo, struct sockaddr_in endereco, int sock_fd) { int quantidadeEnviada; struct QUERYHIT resposta; int out_sz; //Setar os dados da resposta bzero (&resposta, sizeof (resposta)); resposta.DescriptorID = corpo.DescriptorID; resposta.TTL = corpo.Hops; resposta.Hops = 0; resposta.endereco = meuaddr; resposta.tamanhoArquivo = (long) calculaTamanhoArquivo (nomeArquivo); strcpy (resposta.nomeArquivo, nomeArquivo); printf printf printf printf printf printf printf ("\nEnviando QUERYHIT\n"); ("Nome do arquivo encontrado:%s\n", resposta.nomeArquivo); ("O ID da mensagem que veio:%d\n", resposta.DescriptorID); ("O TTL da mensagem que vai:%d\n", resposta.TTL); ("O tamanho do arquivo (bytes):%d\n", (int) resposta.tamanhoArquivo); ("O Hop do descritor que veio:%d\n", corpo.Hops); ("O endereco para o qual devo responder:%s\n", inet_ntoa (endereco.sin_addr)); printf ("Porta para a qual devo responder:%d\n", endereco.sin_port); printf ("O meu endereco:%s\n", inet_ntoa (resposta.endereco.sin_addr)); printf ("Minha porta:%d\n\n", ntohs (resposta.endereco.sin_port)); out_sz = tamanhoQUERYHIT + strlen (resposta.nomeArquivo); //Enviar os dados de resposta quantidadeEnviada = sctp_sendmsg (sock_fd, &resposta, out_sz, (struct sockaddr *) &endereco, 176 Apêndice B. Código fonte do aplicativo Octopus sizeof (struct sockaddr), 0, MSG_UNORDERED, 4, 0, 0); if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar a mensagem\n"); } printf ("Quantidade enviada como QUERYHIT:%d\n", quantidadeEnviada); } //Fim de enviaQUERYHIT //----------------------------------------------------------------//Funcao que trata as mensagens QUERYHIT //Ou exibe a resposta na tela ou efetua o backtrack //----------------------------------------------------------------void trataQUERYHIT (struct QUERYHIT resposta) { int indiceLista; int out_sz; int quantidadeEnviada; char endereco[30]; char porta[10]; char tamanho[50]; char *texto[] = { resposta.nomeArquivo, endereco, porta, tamanho }; printf printf printf printf printf printf printf ("\nRECEBI UM QUERYHIT\n"); ("Nome do arquivo que encontrei:%s\n", resposta.nomeArquivo); ("O ID do descritor que veio:%d\n", resposta.DescriptorID); ("O TTL da mensagem:%d\n", (resposta.TTL - 1)); ("O Hop do descritor que veio:%d\n", (resposta.Hops + 1)); ("O tamanho do arquivo:%d\n", (int) resposta.tamanhoArquivo); ("O endereco do sujeito que tem o arquivo:%s\n", inet_ntoa (resposta.endereco.sin_addr)); printf ("A porta do sujeito:%d\n\n", ntohs (resposta.endereco.sin_port)); strcpy (endereco, inet_ntoa (resposta.endereco.sin_addr)); sprintf (porta, "%d", ntohs (resposta.endereco.sin_port)); sprintf (tamanho, "%d", (int) resposta.tamanhoArquivo); //Caso a resposta seja deste no mesmo //Exibe as informacoes na lista da interface grafica if (aRespostaMinha (resposta.DescriptorID) == 1) { adicionaResposta (texto); linhaListaLocal = 0; printf ("Essa resposta pertence a este no\n"); } else //resposta de outro no { printf ("A resposta e de outro computador - Backtrack\n"); indiceLista = retornaIndiceLista (resposta.DescriptorID); resposta.TTL--; resposta.Hops++; out_sz = tamanhoQUERYHIT + strlen (resposta.nomeArquivo); //Enviar os dados do backtrack quantidadeEnviada = sctp_sendmsg (listaDeIDs[indiceLista].NumeroSocket, &resposta, out_sz, (struct sockaddr *) &(listaDeIDs[indiceLista].endereco), sizeof (struct sockaddr), 0, MSG_UNORDERED, 4, 0, 0); if (quantidadeEnviada == (-1)) { 177 Apêndice B. Código fonte do aplicativo Octopus printf ("Falha ao enviar a mensagem\n"); } } } //Fim do else de resposta de outro no //Fim trataQUERYHIT //----------------------------------------------------------------//Funcao que trata as mensagens do tipo QUERY //----------------------------------------------------------------void trataQUERY (struct QUERY corpo, struct sockaddr_in cliaddr, int sock_fd) { printf ("\nRECEBI UMA QUERY\n"); printf ("DescriptorID:%d\n", corpo.DescriptorID); printf ("TTL:%d\n", corpo.TTL); printf ("Hops:%d\n", corpo.Hops); printf ("Consulta:%s\n", corpo.consulta); printf ("Enviado por:%s\n\n", inet_ntoa (cliaddr.sin_addr)); //No caso de um dos arquivos satisfazer a QUERY //Envia um QUERYHIT struct direct **arquivos; int count; int i; count = scandir ("./ArquivosCompartilhados", &arquivos, NULL, NULL); printf ("\nMeus Arquivos sao:\n"); for (i = 3; i < count + 1; ++i) { printf ("%s\n", arquivos[i - 1]->d_name); if (strstr (arquivos[i - 1]->d_name, corpo.consulta) != NULL) { printf ("Este arquivo satisfaz:%s\n", arquivos[i - 1]->d_name); enviaQUERYHIT (arquivos[i - 1]->d_name, corpo, cliaddr, sock_fd); } } //Propagando a consulta para os outros nos int out_sz; int quantidadeEnviada; struct sockaddr_in servaddr; out_sz = tamanhoCabecalho + strlen (corpo.consulta); //Descarta o pacote se o TTL esgotou if ((corpo.TTL) == 0) return; else { (corpo.TTL)--; (corpo.Hops)++; } //Para cada associacao no cache de associacoes //envia uma QUERY, menos para aquela que enviou a consulta for (i = 1; i <= numeroDeAssociacoesAbertas; i++) { servaddr = associacoesAbertas[i].endereco; if (enderecoIgual (servaddr, cliaddr) != 1) { quantidadeEnviada = sctp_sendmsg (associacoesAbertas[i].numeroSocket, &corpo, out_sz, (struct sockaddr *) &servaddr, 178 Apêndice B. Código fonte do aplicativo Octopus sizeof (struct sockaddr), 0, MSG_UNORDERED, 3, 0, 0); if (quantidadeEnviada == (-1)) { printf ("Falha ao enviar a mensagem\n"); } } } printf ("Quantidade da QUERY enviada:%d\n", quantidadeEnviada); printf ("Para o endereco:%s\n", inet_ntoa (servaddr.sin_addr)); } //fim do If //fim do for de envio de mensagens QUERY //Fim trataQUERY //----------------------------------------------------------------------------//Compara se dois enderecos IP sao iguais //Retorna 1 se for igual, retorna 0 caso contrario //----------------------------------------------------------------------------int enderecoIgual (struct sockaddr_in endereco1, struct sockaddr_in endereco2) { char endereco1Texto[16]; char endereco2Texto[16]; bzero (endereco1Texto, 16); bzero (endereco2Texto, 16); strcpy (endereco1Texto, inet_ntoa (endereco1.sin_addr)); strcpy (endereco2Texto, inet_ntoa (endereco2.sin_addr)); if (strcmp (endereco1Texto, endereco2Texto) == 0) return 1; else return 0; } //Fim de enderecoIgual //----------------------------------------------------------------------------//Checa se jah existe uma associacao dessa no cache de associacoes //Retorna 1 se existe, e 0 caso contrario //----------------------------------------------------------------------------int existeEnderecoLista (struct sockaddr_in endereco) { int i; for (i = 1; i <= numeroDeAssociacoesAbertas; i++) { if (enderecoIgual (associacoesAbertas[i].endereco, endereco) == 1) return 1; } return 0; } //fim existeEnderecoLista //----------------------------------------------------------------------------//Checa se jah existe um socket no cache de associacoes //Retorna 1 se existe, e 0 caso contrario //----------------------------------------------------------------------------int existeSocketLista (int numSocket) { int i; for (i = 1; i <= numeroDeAssociacoesAbertas; i++) { 179 Apêndice B. Código fonte do aplicativo Octopus if (numSocket == (associacoesAbertas[i].numeroSocket)) return 1; } return 0; } //fim de existeSocketLista //----------------------------------------------------------------------------//Remove um endereco do cache de associacoes //Se sucesso, retorna 1, senao 0 //----------------------------------------------------------------------------int removeEnderecoLista (int sock_fd) { if (numeroDeAssociacoesAbertas == 0) return 0; if (numeroDeAssociacoesAbertas == 1) { numeroDeAssociacoesAbertas--; printf ("Removi no do cache de associacoes\n"); return 1; } int i; for (i = 1; i <= numeroDeAssociacoesAbertas; i++) { if (sock_fd == (associacoesAbertas[i].numeroSocket)) { if (i == numeroDeAssociacoesAbertas) { numeroDeAssociacoesAbertas--; printf ("Removi no do cache de associacoes\n"); return 1; } else { associacoesAbertas[i] = associacoesAbertas[numeroDeAssociacoesAbertas]; numeroDeAssociacoesAbertas--; printf ("Removi\n"); return 1; } } //fim do if } //fim do for return 0; } //fim removeEnderecoLista //----------------------------------------------------------------//Imprime as associacoes estabelecidas e ativas //----------------------------------------------------------------void imprimeAssociacoesAbertas (void) { int i; printf ("CACHE DE ASSOCIACOES\n"); for (i = 1; i <= numeroDeAssociacoesAbertas; i++) { printf ("\nAssociacao numero:%d\n", i); printf ("Descritor de socket de numero:%d\n", associacoesAbertas[i].numeroSocket); printf ("Endereco:%s\n", inet_ntoa (associacoesAbertas[i].endereco.sin_addr)); printf ("Porta:%d\n\n", ntohs (associacoesAbertas[i].endereco.sin_port)); } 180 Apêndice B. Código fonte do aplicativo Octopus return; } //fim de imprimeAssociacoesAbertas //----------------------------------------------------------------//Iniciacao do cache de ID de mensagens //----------------------------------------------------------------void iniciaListaLoop () { int i; for (i = 0; i <= 200; i++) { listaDeIDs[i].ID = 0; } } //fim iniciaListaLoop //----------------------------------------------------------------//Funcao para conferir se uma mensagem efetuou loop dado o ID //Retorna 0 caso nao efetuou um loop e 1 caso tenha efetuado //----------------------------------------------------------------int trataLoop (int ID) { int i; for (i = 0; i <= 200; i++) { if (listaDeIDs[i].ID == ID) return 1; } return 0; } //fim trataLoop //----------------------------------------------------------------//Retorna 1 caso conste o ID na lista e ainda tenha saido daqui //Encerra o processo de backtrack //----------------------------------------------------------------int aRespostaMinha (int ID) { int i; for (i = 0; i <= 200; i++) { if ((listaDeIDs[i].ID == ID) && (enderecoIgual (listaDeIDs[i].endereco, meuaddr) == 1)) return 1; } return 0; } //fim aRespostaMinha //----------------------------------------------------------------//Caso o DescriptorID esteja na lista //retorna a posicao dele dentro da lista, senao retorna 500 //----------------------------------------------------------------int retornaIndiceLista (int ID) { int i; for (i = 0; i <= 200; i++) { if (listaDeIDs[i].ID == ID) return i; } 181 Apêndice B. Código fonte do aplicativo Octopus return 500; } //fim retornaIndiceLista //----------------------------------------------------------------//Caso o endereco exista no cache de associacoes //retorna o indice dele dentro do cache //----------------------------------------------------------------int retornaIndiceListaEndereco (struct sockaddr_in endereco) { int i; for (i = 0; i <= 200; i++) { if (enderecoIgual (endereco, associacoesAbertas[i].endereco) == 1) return i; } return 500; } //fim retornaIndiceListaEndereco //----------------------------------------------------------------//Adiciona um numero no cache de ID de associacoes //----------------------------------------------------------------void incluiListaLoop (int ID, int NumeroSocket, struct sockaddr_in endereco) { listaDeIDs[indiceListaLoop].ID = ID; listaDeIDs[indiceListaLoop].NumeroSocket = NumeroSocket; listaDeIDs[indiceListaLoop].endereco = endereco; indiceListaLoop++; if (indiceListaLoop == 201) indiceListaLoop = 0; } //fim incluiListaLoop //----------------------------------------------------------------//Adiciona as respostas QUERYHIT na interface grafica //----------------------------------------------------------------void adicionaResposta (char *texto[]) { gtk_clist_prepend (GTK_CLIST (listaRemota), texto); gtk_widget_show (listaRemota); return; } //fim adicionaResposta //----------------------------------------------------------------//Calcula o tamanho, em bytes, do repositorio de arquivos //----------------------------------------------------------------int tamanhoDiretorio (void) { struct direct **arquivos; int count; int tamanhoFinal = 0; int i; char nomeArquivo[100]; count = scandir ("./ArquivosCompartilhados", &arquivos, NULL, NULL); for (i = 2; i < count; i++) { bzero (nomeArquivo, 100); strcpy (nomeArquivo, ""); 182 Apêndice B. Código fonte do aplicativo Octopus strcat (nomeArquivo, arquivos[i]->d_name); tamanhoFinal = tamanhoFinal + calculaTamanhoArquivo (nomeArquivo); } return tamanhoFinal; } //fim tamanho Diretorio //----------------------------------------------------------------//Checa se um determinado endereco existe no host cache //Retorna 1 se existe e 0 se naum existe //----------------------------------------------------------------int existeHostCache (struct sockaddr_in enderecoComparar) { struct sockaddr_in endereco; hostCacheFile = fopen ("./hostCacheFile.dat", "rb"); if (hostCacheFile == NULL) { printf ("Erro ao abrir o arquivo do Host Cache\n\n"); return 0; } while (!feof (hostCacheFile)) { bzero (&endereco, sizeof (endereco)); fread (&endereco, sizeof (endereco), 1, hostCacheFile); if (enderecoIgual (endereco, enderecoComparar) == 1) { fclose (hostCacheFile); return 1; } } fclose (hostCacheFile); return 0; } //fim de existeHostCache //----------------------------------------------------------------//Inclui um endereco no host cache //retorna 1 em sucesso ou 0 em falha //----------------------------------------------------------------int incluiHostCache (struct sockaddr_in enderecoIncluir) { struct sockaddr_in endereco = enderecoIncluir; if (existeHostCache (endereco) == 1) return 0; int erro; hostCacheFile = fopen ("./hostCacheFile.dat", "ab"); if (hostCacheFile == NULL) { printf ("Erro ao abrir o arquivo do Host Cache\n\n"); return 0; } erro = fwrite (&endereco, sizeof (endereco), 1, hostCacheFile); if (erro == 0) { printf ("Erro ao inserir um arquivo no host cache\n"); fclose (hostCacheFile); return 0; } fclose (hostCacheFile); return 1; 183 Apêndice B. Código fonte do aplicativo Octopus } //fim incluiHostCache //----------------------------------------------------------------//Funcao para imprimir os enderecos do Host Cache //----------------------------------------------------------------void imprimeHostCache (void) { struct sockaddr_in endereco; hostCacheFile = fopen ("./hostCacheFile.dat", "rb"); if (hostCacheFile == NULL) { printf ("Erro ao abrir o arquivo do Host Cache\n\n"); return; } printf ("\nImprimindo HOST CACHE\n\n"); while (!feof (hostCacheFile)) { bzero (&endereco, sizeof (endereco)); fread (&endereco, sizeof (endereco), 1, hostCacheFile); if (strcmp (inet_ntoa (endereco.sin_addr), "0.0.0.0") != 0) { printf ("Endereco:%s\n", inet_ntoa (endereco.sin_addr)); printf ("Porta:%d\n\n", ntohs (endereco.sin_port)); } } fclose (hostCacheFile); } //fim de imprimeHostCache //----------------------------------------------------------------//Funcao para ativar a associacao com todos os enderecos do //host cache //----------------------------------------------------------------void associaHostCache (void) { int erro; struct sockaddr_in endereco; hostCacheFile = fopen ("./hostCacheFile.dat", "rb"); if (hostCacheFile == NULL) { printf ("Erro ao abrir o arquivo do Host Cache\n\n"); return; } while (!feof (hostCacheFile)) { bzero (&endereco, sizeof (endereco)); fread (&endereco, sizeof (endereco), 1, hostCacheFile); if (strcmp (inet_ntoa (endereco.sin_addr), "0.0.0.0") != 0) { printf ("Tentando se associar ao no do host cache:\n"); printf ("Endereco:%s\n", inet_ntoa (endereco.sin_addr)); printf ("Porta:%d\n", ntohs (endereco.sin_port)); erro = associaPeer (endereco); if (erro == 0) printf ("Erro ao associar a este no\n\n"); else printf ("Sucesso ao associar a este no\n\n"); } } //fim do while feof 184 Apêndice B. Código fonte do aplicativo Octopus fclose (hostCacheFile); } //Fim de associaHostCache //----------------------------------------------------------------//Calcula o tamanho de um determinado arquivo //----------------------------------------------------------------int calculaTamanhoArquivo (char *nomeArquivo) { char nomeArquivoCompleto[120]; int tamanhoArquivo = 0; int n; FILE *arquivo; char buffer[200]; bzero (nomeArquivoCompleto, 120); strcat (nomeArquivoCompleto, "./ArquivosCompartilhados/"); strcat (nomeArquivoCompleto, nomeArquivo); arquivo = fopen (nomeArquivoCompleto, "rb"); if (arquivo == NULL) { printf ("Deu problema ao abrir o arquivo\n"); pthread_exit (0); } while (!(feof (arquivo))) { bzero (buffer, 200); n = fread (buffer, sizeof (char), 200, arquivo); tamanhoArquivo = tamanhoArquivo + n; } //End while fclose (arquivo); return tamanhoArquivo; } //Fim calculaTamanhoArquivo //----------------------------------------------------------------//Funcao para separar os tokiens do servidor HTTP //----------------------------------------------------------------//Retorna o nome do arquivo de pedido void retornaNomeArquivoGET (char *entrada, char *saida) { int i = 0; int j = 0; int barra = 0; while (entrada[i] != ’\0’) { if (entrada[i] == ’/’) { barra++; i++; continue; } if (barra == 2) { saida[j] = entrada[i]; j++; } i++; } saida[j] = ’\0’; 185 Apêndice B. Código fonte do aplicativo Octopus } //fim de retornaNomeArquivoGET //Retorna o nome do arquivo da segunda mensagem de negociacao void retornaSeguinte (char *entrada, int doisPontos, char *saida) { int i = 0; int j = 0; int doisPontosInternos = 0; while (entrada[i] != ’\0’) { if (entrada[i] == ’:’) { doisPontosInternos++; i++; } if (doisPontosInternos == doisPontos) { saida[j] = entrada[i]; j++; if (entrada[i + 1] == ’\r’) break; } i++; } saida[j] = ’\0’; } //fim de retornaSeguinte //----------------------------------------------------------------//Funcao para contar o numero de arquivos num diretorio //----------------------------------------------------------------int contaArquivosDiretorio (void) { struct direct **arquivos; int count; count = scandir ("./ArquivosCompartilhados", &arquivos, NULL, NULL); count = count - 2; return count; } //Fim de contaArquivosDiretorio //----------------------------------------------------------------//Fim de SERVENTE.c //----------------------------------------------------------------- 186 Anexo A A interface gráfica do aplicativo NIST Net O aplicativo NIST Net consiste de duas partes: um módulo de kernel carregável, que se conecta ao código de kernel que implementa as funções de rede e do relógio do sistema, além de exportar uma série de APIs; um conjunto de interfaces que utilizam as APIs para a configuração e o controle das operações do emulador. Estas interfaces podem ser usadas em modo texto, ou em modo gráfico. A interface gráfica, como mostrada na figura a seguir, torna simples a configuração dos parâmetros do aplicativo. Os campos “Source” e “Dest” são configurados com os endereços IP de origem e destino, respectivamente, que identificam os pacotes que devem ser interceptados e tratados pelo aplicativo. Podem ser configurados os parâmetros de atraso e variação de atraso de pacotes, limitação de largura de banda, taxa e variação da taxa de perda, dentre outros. 187