Быстрый и понятный способ анализа пакетов

#c #linux

#c #linux

Вопрос:

У меня около 150 различных типов пакетов с идентификаторами 0 — 149. Каждый пакет имеет соответствующую функцию синтаксического анализа.
Всякий раз, когда приходит пакет, я должен вызвать соответствующую ему функцию синтаксического анализа.
Насколько я знаю, есть 3 хороших способа сделать это

  1. проверяйте коды операций и функции вызова внутри 150 условий if-else
  2. 150 случаев переключения
  3. массив указателей на функции с идентификатором пакета в качестве индекса

У меня нет никаких ограничений по памяти, а сервер также очень быстрый.Так что потеря нескольких циклов процессора не является большой проблемой.

Есть ли лучший способ сделать это? Если нет, какой из вышеперечисленных методов является лучшим?
ПРИМЕЧАНИЕ: я использую C, а не C

Комментарии:

1. «У меня нет никаких ограничений в памяти, и сервер также чрезвычайно быстр». переводится как «Кого это волнует? Попробуйте что-нибудь и посмотрите, как это работает «. switch Вероятно, это может сделать A, если не какое-то регулярное выражение.

2. Честно говоря, похоже, что настоящая проблема здесь не в производительности, а в управлении сложностью. Если вы сможете придумать модель внедрения зависимостей для этого, это значительно упростит добавление и удаление обращений, чем перебор с if операторами и switch обращениями. Лично я бы предпочел иметь какой-нибудь легкий инструмент, который автоматически генерирует некоторый код на основе шаблона, чтобы при добавлении новых обращений код перестраивался соответствующим образом. Здесь может помочь немного Python, Perl или Ruby, или, в крайнем случае, инструмент поддержки, скомпилированный на C.

3. Вариант 3. хорошо работает — простой, эффективный, легко расширяемый

4. Я бы поступил так, как сказал @tadman. В прошлом я делал это с помощью awk. Быстро и просто

Ответ №1:

Я опишу способ, которым я делал это в прошлом, для анализа сообщений между Unix и мэйнфреймом. Тогда это казалось простым и легким в управлении. Использовался годами. Здесь я просто копирую части этого кода. Надеюсь, это может быть полезно

короче говоря

На самом деле это всего лишь 8-строчный awk скрипт и механика вокруг него. Эта статья — просто описание системы, которая хорошо работала для меня и может быть легко адаптирована: awk генерирует очень простой заголовочный файл, который связывает функции и идентификаторы, и он интегрирован в процесс сборки, поэтому, если мы что-то изменим или добавим новую функцию, он обновляется сам по себе. Почему awk ? ну, я верю в это A , W и K люди настоящие, и это очень хорошо работает с шаблонами. Но я написал точно такой же файл .h для создания справочных таблиц на C или C или java или Python, когда awk это не вариант

как это работает

Я использовал таблицу указателей на функции и простую схему аннотаций в исходном коде. Для этого примера функции такие, как function_3() показано ниже

 // function_3 @packet-id 149
// function_3 @packet-id 0

int     function_3(Packet* p) // @packet-id 3
{
    printf("Hi from function_3(), id is %dn", (int) p->id);
    return 0;
};
  
  • Аннотация находится @packet-id в той же строке, что и имя функции, как function_3() указано выше
  • Когда одной и той же функцией обрабатывается более одного идентификатора, я использовал комментарий типа
 // function_3 @packet-id 149
  

часто непосредственно перед самой функцией. В данном случае function_3() будут вызываться пакеты с id 3, 149 и 0

вызов функции синтаксического анализа на основе идентификатора пакета

Я использовал вызов по идентификатору, например

 int     callById(const Id, const VFT*);
  

где Id — тип сообщения и VFT справочная таблица, где позиция N — это адрес функции, которая анализирует Id N .

On startup a function

 VFT*    build_VFT(const unsigned);
  

builds the array based on the annotations in the source code. I will post a small example below

structs for the example

 typedef uint8_t Id;

typedef struct
{
    Id      id;
    void*   data;

}   Packet;

typedef int (*Packet_care)(Packet*);

typedef struct
{
    unsigned     nIds;
    Packet_care* fn;

}   VFT;   

int     function_1(Packet*); 
int     function_2(Packet*);
int     function_3(Packet*);

VFT*    build_VFT(const unsigned);
VFT*    delete_VFT(VFT*);
int     callById(const Id, const VFT*);
  

Packet_care является указателем на функцию, которая получает указатель на a Packet и возвращает an int . Мой случай был немного сложнее, но приведенный здесь код — всего лишь тестовый пример, поэтому я оставил здесь только минимум: Packet вот только Id и некоторые данные. VFT имеет только счетчик и таблицу указателей

Анализ аннотаций

Здесь речь идет о C и Linux, поэтому awk не совсем не по теме…

 BEGIN       { found = 0 }
/@packet-id/{ 
       fName =  $2
       if ( fName ~ /(/ )
       {
           fName = gensub( /(. )((. )/, "\1", 1, $2)
       }
       ref = 0
       for( i = 3; i < NF; i  )
       {
           if ($i ~ "@packet-id" )
           {
               ref = i 1
               i = NF   1
           }
       }
       print ARRAY, "[", $ref, "] = ", fName, ";"
       found = found   1
   }
END {   exit found    }
  

Это наверняка можно было бы написать на C или на любом другом языке, но awk было написано для тех же людей, которые одновременно писали C, и использовалось только для такого рода вещей. И это так просто…

  • программа просто ищет строки с выбранной аннотацией — @packet-id здесь — и принимает следующую строку в качестве Id номера. ARRAY является аргументом выполнения и дает имя массива в VFT источнике. Вывод — это просто таблица F[ID] = N , в которой F указано имя массива, ID идентификатор пакета и N имя функции.
  • скрипт создает файл, который включается в компиляцию программы. Это однострочная команда. Но я покажу здесь сценарий оболочки из 1 строки, который легче читать:
  ./build_ref.sh test.h from vft.c using "vft->fn"
  

И он делает именно это: строит "ref.h" с использованием аннотаций "vft.c" , предполагая, что VFT объявлено как vft , а массив указателей есть fn[] .

Единственная команда внутри скрипта

 awk -v ARRAY=${5} -f t.awk.sh ${3} > ${1} 
  

просто чтобы свести концы с концами.

тестовый запуск

 Processing packets...

Will call function for packet id 0
Hi from function_3(), id is 0
function returned 0 for id 0

Will call function for packet id 1
Hi from function_1(), id is 1
function returned 0 for id 1

Will call function for packet id 2
Hi from function_2(), id is 2
function returned 0 for id 2

Will call function for packet id 3
Hi from function_3(), id is 3
function returned 0 for id 3
function returned -1 for id 4
function returned -1 for id 5

Will call function for packet id 149
Hi from function_3(), id is 149

Will call function for packet id 22
Hi from function_2(), id is 22

Will call function for packet id 12
Hi from function_2(), id is 12
  

вот main.c

 #include "vft.h"
int main(void)
{
    VFT* vft = build_VFT(150);
    printf("nProcessing packets...n");
    for (int i = 0; i <= 5; i  = 1)
        printf("function returned %d for id %dn",
            callById( (Id) i, vft), i);

    callById(149,vft);
    callById(22,vft);
    callById(12,vft);
    vft = delete_VFT(vft);
    return 0;
};
  

Создается таблица, и на основе идентификатора вызываются функции синтаксического анализатора. Таблица будет восстановлена, и программа завершится

Конструктор таблиц

 
VFT*    build_VFT(const unsigned size)
{
    VFT* vft = (VFT*) malloc(sizeof(VFT));
    vft->nIds = size;
    vft->fn = (Packet_care*) malloc ( size*sizeof(Packet_care*) );
    for( int i=0; i<size; i = 1) *(vft->fn   i) = NULL;
    #include "ref.h"
    return vft;
};
  

Выделяются struct необходимые size указатели, указатели определяются на основе аннотаций в исходном коде. Включенный файл является результатом работы скрипта. Простой и понятный

Исходные файлы

Есть несколько файлов:

  • main.c находится выше
  • vft.h — это заголовок
  • vfc.c имеет
 int     function_1(Packet*); 
int     function_2(Packet*);
int     function_3(Packet*);

VFT*    build_VFT(const unsigned);
VFT*    delete_VFT(VFT*);
int     callById(const Id, const VFT*);
  

as expected. Delete_VFT() is trivial. callById() is in fact a one-liner, just a call. but in the example is

 int     callById(const Id id, const VFT* V)
{
    static Packet pck;
    pck.id = id;
    if( (unsigned) id >= V->nIds) return -1;
    if( V->fn[id] == NULL ) return -1;
    printf("nWill call function for packet id %dn", (int) id );
    return (*V->fn[(unsigned)id])(amp;pck);
};
  

Поскольку этот код не работает, пакет создается онлайн только с Id помощью . И вызывается соответствующая функция синтаксического анализа.

Это не было тщательно протестировано. Я просто вырезаю и вставляю из мест здесь. Скомпилирован только под gcc 9.3 с использованием этого makefile

 all:  main 

clear: 
    rm -f main *.o
    $(shell ./build_ref.sh ref.h from vft.c using "vft->fn")

main: main.o vft.o
    cc -o main main.o vft.o

main.o: main.c
    cc -c main.c

vft.o: vft.c vft.h
    cc -c vft.c

vft.c: vft.h
  

Таблица для этого примера

В конце концов, это всего лишь таблица…

 vft->fn [ 1 ] =  function_1 ;
vft->fn [ 12 ] =  function_2 ;
vft->fn [ 22 ] =  function_2 ;
vft->fn [ 2 ] =  function_2 ;
vft->fn [ 149 ] =  function_3 ;
vft->fn [ 0 ] =  function_3 ;
vft->fn [ 3 ] =  function_3 ;
  

все файлы

Ниже приведен один файл со всем текстом. Если вы сочтете полезным, я могу опубликовать ссылку на github

 All files:

//////////////////////////////////////////////////////////////1: vft.h

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

typedef uint8_t Id;

typedef struct
{
    Id      id;
    void*   data;

}   Packet;

typedef int (*Packet_care)(Packet*);

typedef struct
{
    unsigned     nIds;
    Packet_care* fn;

}   VFT;   

int     function_1(Packet*); 
int     function_2(Packet*);
int     function_3(Packet*);

VFT*    build_VFT(const unsigned);
VFT*    delete_VFT(VFT*);
int     callById(const Id, const VFT*);

//////////////////////////////////////////////////////////////2: vft.c

#include "vft.h"

int     function_1(Packet* p) // @packet-id 1
{
    printf("Hi from function_1(), id is %dn", (int) p->id);
    return 0;
};

// function_2 @packet-id 12
// function_2 @packet-id 22
int     function_2(Packet* p) // @packet-id 2
{
    printf("Hi from function_2(), id is %dn", (int) p->id);
    return 0;
};

// function_3 @packet-id 149
// function_3 @packet-id 0
int     function_3(Packet* p) // @packet-id 3
{
    printf("Hi from function_3(), id is %dn", (int) p->id);
    return 0;
};

VFT*    build_VFT(const unsigned size)
{
    VFT* vft = (VFT*) malloc(sizeof(VFT));
    vft->nIds = size;
    vft->fn = (Packet_care*) malloc ( size*sizeof(Packet_care*) );
    for( int i=0; i<size; i = 1) *(vft->fn   i) = NULL;
    #include "ref.h"
    return vft;
};

VFT*    delete_VFT(VFT* vft)
{
    if ( vft == NULL ) return NULL;
    if ( vft->nIds == 0) return NULL;
    free( vft->fn );
    free( vft );
    return NULL;
};

int     callById(const Id id, const VFT* V)
{
    static Packet pck;
    pck.id = id;
    if( (unsigned) id >= V->nIds) return -1;
    if( V->fn[id] == NULL ) return -1;
    printf("nWill call function for packet id %dn", (int) id );
    return (*V->fn[(unsigned)id])(amp;pck);
};

//////////////////////////////////////////////////////////////3: main.c

#include "vft.h"
int main(void)
{
    VFT* vft = build_VFT(150);
    printf("nProcessing packets...n");
    for (int i = 0; i <= 5; i  = 1)
        printf("function returned %d for id %dn",
            callById( (Id) i, vft), i);

    callById(149,vft);
    callById(22,vft);
    callById(12,vft);
    vft = delete_VFT(vft);
    return 0;
};

//////////////////////////////////////////////////////////////4: t.awk.sh

BEGIN       { found = 0 }
/@packet-id/{ 
        fName =  $2
        if ( fName ~ /(/ )
        {
            fName = gensub( /(. )((. )/, "\1", 1, $2)
        }
        ref = 0
        for( i = 3; i < NF; i  )
        {
            if ($i ~ "@packet-id" )
            {
                ref = i 1
                i = NF   1
            }
        }
        print ARRAY, "[", $ref, "] = ", fName, ";"
        found = found   1
    }
END {   exit found    }

//////////////////////////////////////////////////////////////5: build_ref.sh

awk -v ARRAY=${5} -f t.awk.sh ${3} > ${1} 

//////////////////////////////////////////////////////////////6: makefile

all:  main 

clear: 
    rm -f main *.o
    $(shell ./build_ref.sh ref.h from vft.c using "vft->fn")

main: main.o vft.o
    cc -o main main.o vft.o

main.o: main.c
    cc -c main.c

vft.o: vft.c vft.h
    cc -c vft.c

vft.c: vft.h

//////////////////////////////////////////////////////////////7: ref.h for the example

vft->fn [ 1 ] =  function_1 ;
vft->fn [ 12 ] =  function_2 ;
vft->fn [ 22 ] =  function_2 ;
vft->fn [ 2 ] =  function_2 ;
vft->fn [ 149 ] =  function_3 ;
vft->fn [ 0 ] =  function_3 ;
vft->fn [ 3 ] =  function_3 ;