Boa tarde galera,
A alguns messes escrevi um guia do código do HLBR para meu orientador
aqui na universidade. Estou enviando-o como anexo para que quem quiser
possa dar uma olhada. Ele é baseado na versão 1.6 do código. Sempre
que puder vou tentar melhorá-lo e mandar aqui pra vocês. Espero que
gostem! Qualquer dúvida podem mandar pra lista, afinal sua dúvida pode
ser a de outro participante. E quem quiser desenvolver, é só falar!
--
PEdroArthur_JEdi
Nunca acredite num sistema que você não conhece o código fonte!
Never trust a system you don't have sources for!
Após ler as configurações do sistemas, o HLBR entra em seu loop principal para
iniciar a verificação dos pacotes que trafégam em suas interfaces. Esse
comportamento é observável através do arquivo fonte engine/main_loop.c (int
MainLoop()).
A função MainLoop tem por principal função verificar se o sistema está
configurado para usar threads ou não. Em caso afirmativa, MainLoop desviará sua
execução para a função int MainLoopThreaded(), caso contrário, será chamada a
função int MainLoopPoll().
engine/main_loop.c:
362: if (!Globals.UseThreads){
363: for (i = 0 ; i < Globals.NumInterfaces ; i++){
364: if (!Globals.Interfaces[i].IsPollable){
365: printf("Error. All interfaces must be
able to poll in single thread mode.\n");
366: return FALSE;
367: }
368: }
369: return MainLoopPoll();
370: }else{
371: return MainLoopThreaded();
372: }
MainLoopThread tentará iniciar uma thread para cada interface de rede que será
usada pelo HLBR, retonando FALSE caso não consiga lançar uma das threads:
engine/main_loop.c:
337: for (i = 0 ; i < Globals.NumInterfaces ; i++)
338: if (!StartInterfaceThread(i)){
339: printf("Couldn't start thread for interface\n");
340: return FALSE;
341: }
Apartir desse momento, o HLBR começará a aceitar pacotes para análise.
MainLoopThreaded chamará a função void ProcessPacketThread(void *), a qual é
reponsável por manter um laço infinito, representando uma relação de
produtor/consumidor com os threads das interfaces em meio ao uso da fila de
pacotes pendentes (Globals.Packets[MAX_PACKETS].status == PACKET_STATUS_PENDING
definido em engine/hlbr.h). O excerto de código reponsável por essa função se
encontra abaixo:
engine/main_loop.c:
242: while (!Globals.Done) {
243: PacketSlot = PopFromPending();
244:
245: if (PacketSlot != PACKET_NONE) {
246: ProcessPacket(PacketSlot);
247: } else {
248: IdleFunc();
249: }
250: }
Podemos ver que enquanto a variável Globals.Done não for verdadeira, a thread
em questão ficará aguardando por pacotes. A cada iteração ela irá ceder seu
tempo de processamento para outra thread caso não tenha nenhum pacote
aguardando, ou, dado o número do pacote (int PacketSlot), o fluxo será desviado
para a função int ProcessPacket(int), responsável por encaminhar o pacote para
decodificação (arquivo fonte decoders/decode.c), coletar algumas estatísticas
dos pacotes, chamar as funções de ação, encaminhar o pacote para seu destino e
retornar um slot vazio a fila de pacotes ociosos.
engine/main_loop.c:
191: if (!Decode(Globals.DecoderRoot, PacketSlot)){
192: printf("Error Processing Packet\n");
193: }
O procedimento acima encaminha o pacote para a decodificação.
Globals.DecoderRoot é um inteiro que representa o primeiro decodificador ao
qual um pacote deve passar. Normalmente, Globals.DecoderRoot contém o valor do
DecoderInterface (decoders/decode_interface.c). PacketSlot representa o número
do pacote a ser processado.
engine/main_loop.c:
196: PacketSec++;
198: if (GetDataByID(PacketSlot, TCPDecoderID, &data))
199: TCPSec++;
200: else if (GetDataByID(PacketSlot, UDPDecoderID, &data))
201: UDPSec++;
203: if (Globals.Packets[PacketSlot].tv.tv_sec != LastTime){
204: Globals.PacketsPerSec = PacketSec;
205: Globals.TCPPerSec = TCPSec;
206: Globals.UDPPerSec = UDPSec;
208: PacketSec = 0;
209: TCPSec = 0;
210: UDPSec = 0;
212: LastTime = Globals.Packets[PacketSlot].tv.tv_sec;
213: }
Coleta estatísticas sobre a rede com informações baseadas nos contadores dos
pacotes.
engine/main_loop.c
215: if (!BitFieldIsEmpty(p->RuleBits,Globals.NumRules)){
219: if (!PerformActions(PacketSlot)){
220: printf("Failed to execute the actions\n");
221: }
222: }
Verifica se alguma regra conferiu com o pacote.
engine/main_loop.c
224: RouteAndSend(PacketSlot);
Envia o pacote pela rede.
engine/main_loop.c
225: ReturnEmptyPacket(PacketSlot);
Retorna o pacote vazio a lista de ociosos.
Dentre os procedimentos chamados acima, int Decode(int, int), definido em
decoders/decode.c, tem predominância no comportamento do HLBR. Ele é
responsável por encaminhar recursivamente os pacotes por entre os
decodificadores até que se tenha uma decisão quanto ao destino do pacote. O
código reproduzido abaixo representa a chamada de decodificação do pacote:
decoders/decode.c:
277: p->DecoderInfo[DecoderID].Data =
Globals.Decoders[DecoderID].DecodeFunc(PacketSlot);
Para entender melhor o que está acontecendo, devemos compreender o que é um
decoder. Um decoder nada mais é que uma estrutura de dados onde são armazenados
o nome do decoder, seu ID, sua mascará de dependência, testes associados,
módulos associados, decoders filhos, decoder pai, próximo filho (campo acessado
para escrita apenas pela função int DecoderAddDecoder(int, int)), uma função de
decodificação, uma função de dealocação, uma função de configuração e sua flag
de atividade. A estrura de dados encontra-se definida no arquivo fonte
engine/hlbr.h, e reproduzida abaixo:
engine/hlbr.h:
249: typedef struct decoder_rec{
250: char Name[MAX_NAME_LEN];
251: int ID;
252: unsigned char DependencyMask[MAX_RULES/8];
253: struct test_rec* Tests;
254: struct module_rec* Modules;
255: struct decoder_rec* Children;
256: struct decoder_rec* Parent;
257: struct decoder_rec* NextChild;
259: void* (*DecodeFunc) (int PacketSlot);
260: void (*Free) (void *pointer);
261: int (*ConfigFunction) (FILE *fp);
263: char Active; /*true if anything
actually uses it*/
264: } DecoderRec;
Outra estrutura de dados importante para que possamos prosseguir é a PacketRec
(engine/hlbr.h). PacketRec possui todas as informações relevantes a um pacote.
Dentre elas, as mais importantes são RawPacket, representando o pacote de dados
em forma bruta, BeginData, que representa o offset onde o primeiro byte não
decodificado se encontra, PacketLen, comprimento do pacote, PassRawPacket, flag
de bloqueio, e sua mascára de bits.
engine/hlbr.h:
160 typedef struct packet_rec {
161 int PacketSlot;
162 unsigned int PacketNum;
164 int InterfaceNum;
165 int TargetInterface;
167 unsigned char* RawPacket;
168 char Pad[2];
169 unsigned char
TypicalPacket[TYPICAL_PACKET_SIZE];
170 char LargePacket;
171 int PacketLen;
173 unsigned char RuleBits[MAX_RULES/8];
174 struct timeval tv;
176 DecoderData DecoderInfo[MAX_DECODER_DEPTH];
177 int DecodersUsed[MAX_DECODERS];
178 int NumDecoderData;
180 int BeginData;
183 char PassRawPacket;
184 int SaveCount;
187 char Status;
189 pthread_mutex_t Mutex;
190 int LockID;
192 struct port_pair* Stream;
193 } PacketRec;
Com isso, podemos ver que o código executado na linha 277 de decoders/decode.c
coleta os dados importantes do pacote para um dado decoder. Caso o pacote seja
irrelevante ao decoder em questão (sua decodificação retorne NULL), todas as
regras que dependem desse decoder são marcadas como inativa. Isso ocorre em:
decoders/decode.c
281: if (p->DecoderInfo[DecoderID].Data) {
/* And the code Flows */
307: } else {
309: NotAndBitFields(p->RuleBits,
Globals.Decoders[DecoderID].DependencyMask, p->RuleBits, Globals.NumRules);
310: return TRUE;
311: }
Caso contrário, o pacote possua informações relevantes ao decoder, esse último
é marcado como em uso e a função se encarrega de chamar os testes associados:
decoders/decode.c
287: p->DecodersUsed[p->NumDecoderData++] = DecoderID;
290: test = Globals.Decoders[DecoderID].Tests;
292: while (test) {
293: if (test->Active)
294: if (test->TestFunc)
295: test->TestFunc(PacketSlot,
test->TestNodes);
296: test = test->Next;
297: }
Testes também são estrutura de dados. TestRec armazena o nome do teste, o nome
dos testes na escrita de regras, seu ID, o ID do decodificador associado, os
nós de teste (estrutura explicada mais abaixo), um ponteiro para o proximo
teste, a mascára de dependência, uma função para adição de nós de teste, a
função de teste, uma função para finalizar a adição de testes e uma função
reservada para uso futuro.
engine/hlbr.h:
220: typedef struct test_rec{
221: char Name[MAX_NAME_LEN];
222: char ShortName[MAX_NAME_LEN];
223: int ID;
224: int DecoderID;
225: char Active;
226: TestNode* TestNodes;
227: struct test_rec* Next;
228: unsigned char DependencyMask[MAX_RULES/8];
230: int (*AddNode)(int TestID, int
RuleID, char* Args);
231: int (*FinishedSetup)();
232: int (*TestFunc)(int PacketSlot,
TestNode* Nodes);
233: int (*TestStreamFunc)(int
PacketSlot, TestNode* Nodes);
234: } TestRec;
Os arquivos de regras formam os nós de um PacketRec. Como por exemplo:
rules/www.rules
10: <rule>
11: ip dst(www)
12: tcp dst(80)
13: tcp regex(GET[ -~]+\.asp($|/|\&|\?))
14: message=(www-1) .asp request
15: action=action1
16: </rule>
O que dizemos é: ao decodificador IP, adicionar um nó do teste DST com o valor
"www". Ao decodificador TCP, adicionar um nó do teste DST com o valor 80 e
adicionar um nó ao teste REGEX com o argumento "GET[ -~]+\.asp($|/|\&|\?)".
Esses nós são armazenados em estruturas TestNode e enfileirados em meio ao uso
de ponteiros:
engine/hlbr.h
209: typedef struct test_node{
210: int RuleID;
211: void* Data;
212: struct test_node* Next;
213: } TestNode;
Portanto, de forma bastante abstrata, na regra mostrada mais acima teriámos:
TestNode {10, www, *}
TestNode {10, 80, *}
TestNode {10, GET[ -~]+\.asp($|/|\&|\?), *}
Cada qual associado a seus respectivos decoders.
Voltando para a função Decode, após os testes, é checado se ainda existem
regras a serem conferidas. Caso não haja, a função retorna e podemos procurar
por outro pacote para analisar, caso haja, devemos passar o pacote adiente,
seguinda a hierárquia dos decodificadores. O seguinte trecho de código
concretiza essa idéia:
decoders/decode.c
326: child = Globals.Decoders[DecoderID].Children;
327: while (child) {
328: if (!Decode(child->ID, PacketSlot)) {
329: fprintf(stderr, "Decoder %s failed\n",
child->Name);
330: }
331: child = child->NextChild;
332: }
Estamos chamando a função Decode recursivamente, passando dessa vez como
parâmetros o DecoderID do decoder filho e o pacote atual. Esse processo irá se
repetir até que a condição de não haver mais regras seja atingida:
decoders/decode.c
314: if (!BitFieldIsEmpty(p->RuleBits, Globals.NumRules)) {
322: return TRUE;
323: }