Protomake: Um Compilador de Protocolos



Baixar 67.83 Kb.
Encontro29.09.2019
Tamanho67.83 Kb.

ETAP: Uma linguagem para implementação de protocolos

Thiago Farias, Igor Cananéa, Fábio Almeida, Rodrigo Araújo

Centro de Informática, UFPE


Av. Prof. Luiz Freire, S/N, Cidade Universitária - Recife, PE

{tsmcf, icc, fga, rca} @cin.ufpe.br

Abstract. The development of network protocols is a non trivial task that demands time and effort. The available tools are currently not usable enough to create application protocols that can use TCP and UDP. Moreover, they are not a great help in the creation of an application that reflects the inital project, mainly because the description languages used tend to be too abstract. This article presents ETAP, a network protocol implementation language that, when compiled, generates equivalent source-code in C and JAVA from a protocol description. A visualization plugin for Ethereal that is able to dissect protocol messages can also be generated to help in protocol debugging. ETAP was created to overcome limitations in TAP, the language that it was based on.

Resumo. O projeto e desenvolvimento de protocolos são tarefas não triviais que demandam tempo e esforço. As ferramentas disponíveis atualmente não são suficientemente completas para descrever protocolos complexos, que podem utilizar TCP e UDP. Além disso, fornecem pouca ajuda na criação de uma aplicação próxima do que foi projetado inicialmente, principalmente pelo fato de suas linguagens de descrição serem muito abstratas. Este artigo ETAP, uma linguagem para implementação de protocolos de rede que, ao ser compilado, gera código-fonte equivalente nas linguagens C e JAVA a partir de uma descrição do protocolo, podendo gerar um plugin que descreve as mensagens dessa descrição para o analisador de protocolos Ethereal. ETAP foi criada para suprir as limitações de TAP, linguagem na qual ela foi baseada.

  1. Introdução

ETAP é uma linguagem projetada para facilitar o desenvolvimento de protocolos de redes. O compilador gera, a partir de uma descrição na linguagem ETAP (descrita na seção 3.1), um conjunto de funções e uma aplicação em uma linguagem alvo, que pode ser qualquer uma dependendo do compilador. Atualmente, as linguagens alvo suportadas são C e Java. As funções podem ser usadas como uma biblioteca de código por outros programas e a aplicação gerada é um exemplo que pode ser usado para testar se a comunicação está funcionando através do protocolo descrito. Para facilitar a depuração do protocolo, o compilador Protomake, feito para compilar ETAP, pode gerar um plugin de visualização para o analisador de protocolos Ethereal [1].

ETAP é uma linguagem simples e sucinta, permitindo ao programador preocupar-se apenas com o funcionamento do protocolo e não como essas funcionalidades serão implementadas. Para facilitar a implementação de funcionalidades que não fazem parte do protocolo diretamente, existe um mecanismo para utilizar funções criadas na linguagem alvo dentro do protocolo. O compilador se encarrega de gerar o código necessário para fazer a integração entre a descrição compilada e o código na linguagem alvo. Por exemplo, para um servidor de echo que envia a mensagem de volta ao cliente de forma cifrada, o desenvolvedor pode implementar a cifragem em um módulo externo e integrá-la à descrição.

Na seqüência desse artigo, a seção 2 apresenta alguns trabalhos que estão relacionados com ETAP; a seção 3 explica como uma descrição de um protocolo deve ser feita; na seção 4 há um exemplo de um servidor de echo que utiliza boa parte dos recursos da linguagem para especificar protocolos. Por fim, a seção 5 apresenta alguns comentários finais e trabalhos futuros.


  1. Trabalhos Relacionados

Serão relacionados nesta seção os trabalhos de maior relevância na área de geração automática de protocolos ou aqueles que influenciaram o desenvolvimento de ETAP, em especial TAP, linguagem que foi a base de desenvolvimento de ETAP.

    1. TAP

ETAP foi inicialmente desenvolvida como uma extensão da linguagem TAP [2] (Timed Abstract Protocol), que é baseada em AP [3] (Abstract Protocol), para estruturar o protocolo. A TAP é baseada em eventos de recepção e timeout, os quais desencadeiam uma seqüência de ações e estados que serão acionados. O código é gerado em C e é possível usar funções escritas em C na especificação do protocolo.

Os protocolos gerados são para a camada de aplicação e fazem uso do protocolo de transporte UDP. O APC (Austin Protocol Compiler, compilador para a linguagem TAP) é o único compilador que efetivamente gera uma aplicação entre os que são descritos neste trabalho. Segundo McGuire [2], até o presente momento, não existe a intenção de fazer uma versão para TCP.



    1. Esterel

Esterel [4] é uma linguagem para programar sistemas reativos e síncronos, também utilizada para validação de protocolos. Além disso, permite a verificação e validação de sistemas reativos e o seu compilador gera sistemas para simular o protocolo criado. Esterel pode ser utilizada para gerar implementações de programas reativos em software ou hardware. O compilador de Esterel também gera código C para ser incorporado como núcleo de um programa maior que manipula dados e interface com o usuário. Embora possa ser usada para construir servidores e clientes de rede, Esterel não é uma linguagem projetada especificamente para isso.

    1. Prolacc

Prolac [5] é uma linguagem orientada a objetos criada para desenvolver protocolos de rede, desde os simples aos mais complexos. Para compilar códigos escritos em prolac foi desenvolvido o compilador prolacc. O objetivo da linguagem é facilitar o desenvolvimento de protocolos sem um compromisso com a corretude do mesmo, deixando isso a cargo do desenvolvedor. Embora seja possível criar protocolos complexos, a descrição é apenas um pouco melhor do que programar o protocolo diretamente em uma linguagem de propósito geral pelo fato dela ser uma linguagem orientada a objetos. Como possui uma sintaxe cheia de detalhes, o desenvolvimento de protocolos é Prolacc é lento e propenso a erros.

    1. Promela++

Promela++ [6] é baseada em uma linguagem de descrição abstrata de protocolos chamada Promela [7] e sua sintaxe é semelhante a de C. É possível criar protocolos integrados (mais de uma camada) e o código gerado é eficiente, em alguns casos até mais do que se houvesse sido escrito em uma linguagem de propósito geral. Promela++ permite a verificação e validação do protocolo ao convertê-lo para Promela. Apesar de ser semelhante à linguagem C, não dispõe de alocação dinâmica de variáveis nem tem suporte a temporizadores.

  1. ETAP

É descrito nesta seção a forma de especificar protocolos usando ETAP, o seu funcionamento e o que é gerado após a compilação do protocolo.

    1. Definição do protocolo

O Protomake é a ferramenta usada para gerar código nas linguagens alvo a partir de uma descrição simples do protocolo, especificada em ETAP, a qual será detalhada ao longo desta seção. A linguagem TAP foi extendida neste trabalho para possibilitar a criação de protocolos que fazem uso de TCP e para poder integrá-la com qualquer linguagem alvo.

ETAP é uma linguagem simples baseada em guardas. Uma guarda possui uma condição (expressão booleana, recepção de mensagens ou timeout) e um conjunto de ações que serão executadas caso a condição seja satisfeita. As mensagens são descritas por uma estrutura de campos que podem ter os tipos string, bits ou bytes. O arquivo de especificação possui cinco seções, que devem ser especificadas nessa ordem: PLUGIN, IMPORTS, MENSAGENS, BEHAVIORS e PROCESSOS, dos quais apenas MENSAGENS e PROCESSOS são obrigatórios. As palavras chaves, os operadores e a BNF da linguagem são apresentadas na seção tal. As subseções 3.1.1, 3.1.2, 3.1.3, 3.1.4 e 3.1.5 descrevem as seções do arquivo de especificação.



      1. Plugin (seção opcional)

O plugin só é gerado se essa descrição estiver no arquivo. Para especificar o plugin, a seguinte construção é usada:

!@PALAVRA-CHAVE VALOR

As palavras chaves válidas são: Author, Email, Name, Acrynom, Port, Transport. A ordem é indiferente. Exemplo:

!@Author "Fred Lins"

!@Email "fredlins@bol.com.br"

!@Name "ProtoMake Temporary Test Protocol"



!@Acronym "PTTP"

!@Port 9986

!@Transport tcp

Com exceção de Port (que deve ser um inteiro entre 0 e 65535) e Transport (que deve ser tcp ou udp), os valores das palavras chaves devem ser strings. Essa seção não é obrigatória, mas, caso exista, ao menos as palavras chaves Name, Acronym, Port e Transport devem ser definidas. As palavras chaves devem ser preenchidas com os seguintes valores:



  • Author: Autor(es) do protcolo.

  • Email: E-mail do(s) autor(es).

  • Name: Nome do protocolo.

  • Acrynom: Abreviatura do protocolo. É usado para dar nome ao arquivo do plugin.

  • Port: A porta em que o protocolo ira operar.

  • Transport: O protocolo de transporte que será usado.

O Protomake usa essa informação para gerar o plugin de visualização para o Ethereal. Esse formato foi definido antes da criação dos tipos client_address e server_address, quando apenas um protocolo de transporte era possível por protocolo. Portanto, essa forma irá mudar.

      1. Imports (seção opcional)

Imports são outros arquivos de especificação que terão suas definições incluídas no arquivo atual. Dever ser especificada da seguinte forma:

import "arquivo"

onde "arquivo" deve ser um arquivo válido, podendo especificar o caminho relativo ou absoluto do mesmo (exemplo: "/home/icc/t.pm"; "../f.pm"; "http.pm").


      1. Mensagens

Mensagens são definidas como registros onde os tipos de cada campo podem ser bits ou bytes. Ao menos uma deve ser definida. Para especificar uma mensagem, usa-se a seguinte construção:

message msg [(in, out)]

begin

campo1 : n bits [= x],



campo2 : n bytes [= "xxxxx"],

campo3 : campo1 bytes,

campo4 : str(10),

campo5 : str("teste"),

campo6 : str(/\n/),

.

.



.

campoM-1 : n bits [= y],

campoM : n bits [= z]

end


O que estiver entre colchetes é opcional. A mensagem começa com a palavra chave message (que pode ser precedida por external), seguida pelo nome da mensagem (nesse caso msg) e opcionalmente funções para tratamento da mensagem (in, out). Em seguida, a palavra chave begin e os campos das mensagens. A mensagem pode conter quantos campos forem necessários, definidos como:

campo : tamanho bits|bytes [ = valor]

ou

campo : str(N)|str(CTE)|str(SEP),



Na primeira forma, caso o tipo seja bits, tamanho deve ser um inteiro entre 1 e 32 e o valor opcional deve ser um inteiro que esteja entre 0 e 2^tamanho-1; caso seja byte, tamanho pode ser um inteiro entre 1 e 2^32-1 ou um campo que foi previamente especificado na mesma mensagem e que tem tipo bit (no caso acima, o tamanho de campo3 será variável, dependendo do valor que estiver armazenado em campo1). Se for um inteiro, pode ter um valor opcional que deverá ser uma string. Com exceção do último campo, todos os campos devem terminar com uma vírgula (,). Caso o campo possua um valor, cada vez que a mensagem for recebida, esse campo será checado para assegurar que o valor foi enviado. Caso contrário, a mensagem é descartada.

Na segunda forma, todos os tipos representam strings. str(N) é uma string de tamanho N (campo4, no caso acima). “N” pode ser qualquer expressão válida que resulta em um inteiro. str(CTE) é uma string constante (campo5, no caso acima). CTE deve ser uma string válida (qualquer coisa entre aspas duplas) e, assim como nos tipos bits e bytes que possuem um valor inicial, cada vez que a mensagem for recebida, esse campo será checado para assegurar que a string foi enviado. Caso contrário, a mensagem é descartada. str(SEP) é uma string de tamanho variável terminado por sep (campo6, nesse caso). SEP é um conjunto de caracteres entre duas barras inclinadas (/abc/, por exemplo). No exemplo acima, campo6 é uma string terminada por \n (código que indica nova linha). Se o SEP de campo6 fosse /ab./, ele seria uma string terminada por ab.

As funções opcionais (in, out) são utilizadas nos buffers de serialização, antes de enviar a mensagem para o socket (out) e quando o buffer de recepção conter alguma mensagem (in). Essas funções são definidas pelo programador, caso sejam especificadas. O Protomake gera apenas o esqueleto delas.


      1. Behaviors (seção opcional)

Behaviors são construções que podem ser usadas como ações de uma guarda do protocolo. O Objetivo dos behaviors é tornar ETAP independente da linguagem alvo. O Protomake, por exemplo, pode gerar código em C e Java a partir de uma especificação em ETAP. Os Behaviors são definidos da seguinte forma:

behavior a1, a2, ..., aN

behavior b1

behavior c1, c2, ..., cM

Mais de um behavior pode ser definido na mesma linha, separados por virgulas. Eles podem ser usados no protocolo, como uma ação, na forma a1.xxxx(expressão), onde a1 é o nome do behavior, xxxx é o nome de uma função pertencente ao behavior e expressão é qualquer expressão válida. Exemplo:

a1.processa(msg.campo1 * 100 / 2)

a1.troca(a, b)

a2.inicializa()

.

.

.



Ao declarar um behavior X, qualquer ação do tipo X.xxxx(expressao) passa a ser válida. O Protomake irá gerar os arquivos com os esqueletos de todas as funções encontradas no protocolo. Cabe ao programador definir o que as funções fazem. Os tipos dos parâemtros são identificados pelo compilador e a passagem de parâmetros e feita sempre por referência (modificações nos parâmetros passados são visíveis ao processo). Os behaviors não retornam valor. Caso seja necessário retornar um valor, passe uma variável como parâmetro.

      1. Processos

O protocolo é definido por suas mensagens e suas seqüências de ações. Para especificar a seqüência de ações de um determinado protocolo, utiliza-se a seguinte construção:

processs nome

[var|const a1, a2, ..., aN : tipo [= init]]

[var|const b1, b2, ..., bM : tipo [= init]]

.

.

.



begin

guarda -> açoes

[guarda -> ações]

.

.



.

end


A seqüência de ações (ou máquina de estados) do protocolo é chamada de process e cada process possui um nome. Todo arquivo de especificação deve conter ao menos um process chamado main. Esse é o processo principal do protocolo. Qualquer outro processo no mesmo arquivo deve ser explicitamente iniciado com a ação start. Isso permite modelar protocolos que, ao serem compilados, possam tratar mais de um cliente ao mesmo tempo.

A especificação de um processo começa com a palavra-chave process seguida de um nome. Em seguida, pode-se especificar variáveis ou constantes que serão usadas no processo. Variáveis de mesmo nome definidos em processos diferentes são canais de comunicação entre eles. O comportamento do canal depende de como ele foi declarado. Por exemplo, supondo q x tenha sido declarado no processo chamador (p1) e no processo chamado (p2). O comportamento do canal x será o seguinte:



  • Se x é variável em p1 e variável em p2: modificações feitas em qualquer processo serão visíveis a ambos.

  • Se x é constante em p1 ou p2: modificações em quem o tiver declarado como variável não será visível em quem o tiver declarado como constante.

Em qualquer caso, o x é inicializado no processo chamado com o valor que ele possui no processo chamador no momento em que o processo chamado é iniciado. Os seguintes tipos são válidos para variáveis e constantes:

  • Integer

  • x..y (range, onde x e y devem ser inteiros)

  • address

  • client_address

  • server_address

  • array[X] of Type (array, onde X deve ser um inteiro e Type um tipo válido)

Se declarados como constantes, os tipos integer, range e array devem ser inicializados. Os tipos address, client_address e server_address (daqui pra frente denominados address) não podem ser inicializados. A semântica dada a variável e constante para o tipo address é a seguinte: se um address é variável, ao ser usado numa cláusula rcv, o endereço de quem enviou o dado é colocado no address. Isso é útil para definir protocolos de servidores, onde não se sabe quem vai enviar o dado. Se for constante, o endereço de quem enviou é comparado com o valor que ele possui. Se for o mesmo, o dado é recebido. Caso contrário, ele é descartado. Os valores dos address são preenchidos quando o programa gerado pelo compilador é compilado.

O corpo do process é formado por ações que serão executadas caso suas guardas sejam satisfeitas. As guardas consistem de expressões booleanas, guardas timeout ou guardas rcv.

As guardas timeout são ativadas toda vez que o contador representado pelo identificador na guarda chega a zero. Estes contadores são inicializados com o comando act. As guardas rcv são ativadas quando uma mensagem (do tipo descrito na guarda) chega no buffer e é devidamente identificada. Em relação à prioridade de ativação dessas guardas tem-se que se nenhuma guarda com expressão booleana for satisfeita, o protocolo espera que alguma mensagem chegue para verificar se uma guarda de rcv pode ser satisfeita.

A ação pode conter os comandos de atribuições, diretivas send, diretivas rcv, diretivas act, behaviors, if (teste de expressão booleana), do (laço de repetição) e expressões aritméticas. A diretiva send é utilizada para enviar mensagens e a rcv para recebê-las, sendo especificadas da seguinte forma:

send mensagem to endereço

rcv mensagem from endereço

O comando if consiste de guardas seguidas de ações. A primeira guarda satisfeita é a que terá sua seqüência de ações executada. Exemplo:

if expr1 ->

ações1

[] expr2 ->



ações2

...


[] exprN ->

açõesN


fi

O laço de repetição é representado nesta linguagem pelo comando do. Sua sintaxe é similar a do comando if, diferindo apenas pela quantidade de guardas permitidas, neste caso só uma. As ações são executadas enquanto a guarda for satisfeita. Exemplo:

do a < 3 ->

a := a + 1;

b := b * b;

b := b + 1

od


    1. Geração de código

Ao compilar a especificação, o código poderá ser gerado em Java ou em C. O código gerado inclui a estrutura da mensagem, as funções do behavior e funções para codificar e decodificar a mensagem. É gerado também o plugin, caso seus dados tenham sido definidos.

Atualmente, apenas as estruturas das mensagens, suas funções de serialização, as funções do behavior e o plugin para o Ethereal são gerados. O motor do protocolo foi definido, porém ainda está sendo implementado. Com o código gerado, é possível escrever um programa que implemente o protocolo e usar as funções de serialização e desserialização para enviar e receber corretamente as mensagens.



    1. Motor do protocolo

O motor do protocolo é composto de duas linhas de execução (threads), a de recepção de mensagens e a da máquina de estados. As duas linhas são executadas segundo o modelo concorrente produtor-consumidor, no qual o produtor é a da recepção, que coloca as mensagens recebidas numa fila, e o consumidor é a da máquina de estados, que remove as mensagens da fila quando é necessário. A remoção de uma mensagem da fila é feita quando é satisfeito um dos seguintes critérios:

  • Nenhuma guarda está ativa, ou seja, nenhuma expressão booleana é verdadeira. Neste caso, a mensagem é retirada da fila e a máquina de estados verifica se ela satisfaz alguma guarda rcv.

  • Um comando rcv é encontrado nas ações (lado direito das guardas).

Se não existirem mensagens para serem consumidas o programa espera bloqueado até que chegue alguma mensagem ou até que uma guarda timeout seja ativada.

O laço principal do protocolo verifica a cada passo os estados para determinar o próximo estado a ser executado. Diferentes fluxos de estados ativos podem ocorrer dependendo das prioridades dadas pelo usuário do programa. Tem-se a possibilidade de dar prioridade à recepção de mensagens. Neste caso, toda mensagem que chega é imediatamente processada e o estado ativo antes da chegada da mensagem é colocado em uma fila, para ser executado depois do tratamento da mesma. A outra possibilidade é dar prioridade à execução do fluxo até que nenhuma guarda fique ativa. Assim, quando uma mensagem chega, ela fica esperando na fila até que o fluxo ativo termine, ou que um comando rcv contido no fluxo seja executado.



    1. Plugin do Ethereal

O Ethereal é um analisador de protocolos muito usado para monitorar pacotes que estão trafegando pela rede. Ele permite uma visualização detalhada do pacote com descrições de seus campos através de plugins de dissecação. O Protomake pode gerar um plugin para o protocolo especificado. Este plugin é simples, porém útil o suficiente para visualizar as mensagens geradas pelo protocolo.

  1. Especificação de um protocolo em ETAP para o Protomake

Será especificado um servidor de echo para melhor ilustrar a criação de um protocolo. Esse servidor possui a característica de cifrar as mensagens antes de devolvê-las à sua origem. Ao receber a mensagem, ela é cifrada pela função encript, pertencente ao behavior Crypt para só depois ser devolvida. Se não for possível cifrar a mensagem, uma mensagem de erro é enviada.

Primeiramente é definido um cabeçalho para que o plugin para o Ethereal seja gerado, como descrito na subseção 3.1:

!@Author "Fred Lins"

!@Email "fredlins@bol.com.br"

!@Name "ProtoMake Crypt Protocol"

!@Acrynom "PCP"

!@Port 9986

!@Transport tcp

Em seguida, é especificada a mensagem que será usada no protocolo:

message text

begin

length : 8 bits,



msg : length bytes

end


Como neste exemplo precisa-se cifrar a mensagem e isto será feito por um componente externo à ETAP, é definido um behavior para referenciar este componente no protocolo:

behavior Crypt

Por último, serão especificados os processos que inicia o protocolo (main) e que trata a mensagem recebida (cryptMsg).

process main

var client : address

begin


rcv text from client -> start cryptMsg

end


process cryptMsg

var client : address;

const started : integer = 1

begin


started = 1 -> Crypt.encrypt(text.msg, text.length);

if text.length <> 0 ->

send text to client

[] text.length = 0 ->

text.msg := "error";

text.length := 5;

send text to client

fi;


stop

end


O processo auxiliar (cryptMsg) cifra e envia a mensagem de volta para o cliente caso não ocorra nenhum erro, caso contrário, o protocolo envia uma mensagem padrão de erro.

  1. Conclusão

Este artigo apresenta a linguagem de propósito específico ETAP, tomando como exemplo a ferramenta Protomake. Esta linguagem foi extendida a partir da linguagem TAP. ETAP apresenta as vantagens de acelerar o processo de desenvolvimento de protocolos provendo maneiras de depurar e gerar o código de mensagens e funções de apoio.

Foi apresentado um exemplo da utilização da linguagem ETAP para uma aplicação simples de um servidor de echo que cifra as mensagens antes de devolvê-las à sua origem. Como pode ser visto no exemplo, a definição do protocolo é enxuta, o que significa que a escrita de um protocolo na linguagem ETAP é rápida e prática. Uma vez que o desenvolvimento de protocolos em uma linguagem de programação é lento e sujeito a falhas (de implementação), já que o código do protocolo a ser gerado é bem maior que sua descrição, torna-se aparente a vantagem da utilização de ETAP.

A semântica da linguagem já está definida, porém a ferramenta de compilação (o Protomake) ainda está incompleta. O Protomake gera apenas o código das mensagens, funções de serialização e desserialização, esqueletos dos behaviors e plugin para o Ethereal.

Como trabalhos futuros pretende-se desenvolver extensões para o Protomake, as quais irão disponibilizar uma interface para geração de protocolos de outras camadas, tais como transporte e enlace. Além disso, é previsto o desenvolvimento da própria máquina de estados do protocolo de entrada e um módulo para sua verificação e/ou validação. Outro ponto interessante a ser ressaltado é a construção de uma interface gráfica amigável, para tornar mais simples a interação com os usuários.



  1. Referências

[1] ETHEREAL, The world's most popular network protocol analyzer, http://www.ethereal.com

[2] MCGUIRE, Tommy Marcus & GOUDA, Mohamed G. The Austin Protocol Compiler, November 17, 2002

[3] GOUDA, Mohamed G. Elements of Network Protocol Design. John Wiley & Sons, 1998.

[4] BERRY, Gérard. The Foundations of Esterel, MIT Press – Foundations of Computing Series, 2000.

[5] KOHLER, Eddie. Prolac: A language for protocol compilatiion, http://www.pdos.lcs.mit.edu/prolac/prolac-the.pdf, 1997.

[6] BASU, Anindya, MORRISETT, J. Gregory & EICKEN, Thorsten von. Promela++: A Language for Constructing Correct and Efficient Protocols, 1998



[7] HOLZMANN, Gerard J. Design and Validation of Computer Protocols, Prentice Hall, 1991.




©aneste.org 2017
enviar mensagem

    Página principal