Hyperscan 之 pcapscan示例解读_技术探索者的博客-程序员秘密

技术标签: hyperscan  网络安全系列  

源码下载:

https://github.com/intel/hyperscan/releases/

1. 概述

此示例实现一个简单的数据包匹配性能测量程序。

pcapscan使用libpcap从pcap文件中读取数据包,并根据一个规则文件中指定的多个正则表达式对报文进行匹配,并输出匹配结果和一些统计信息。pcapscan使用并对比了两种匹配模式:BLOCK和STREAM。BLOCK模式时它对单个数据包进行匹配;而STREAM模式下它通过五元组将数据包进行简单分流,并对每条流中的数据进行匹配。STREAM模式可以命中跨越数据包边界的匹配数据(比如,要匹配abc,而a在前一个数据的末尾,而bc在后一个数据包的前端,这两个数据包在一个流中,那么STREAM模式匹配可以命中它,而BLOCK模式不能)。

此示例演示了以下hyperscan概念:

  1. 多个模式的编译
    与simplegrep示例不同,pcapgrep读取并编译规则文件中的多个正则表达式。编译好的database在运行时可以并行匹配所有模式(而不是一次scan匹配一个)
  2. 流模式匹配
    包括流状态数据的构造以及流模式下匹配回调函数的用法

2. 源码解读

下面按照代码执行的先后顺序对pcapscan源码进行简单解读。

2.1 编译

函数buildDatabase用来编译规则文件中的多个正则表达式,参数mode指定了是BLOCK还是STREAM模式。

static hs_database_t *buildDatabase(const vector<const char *> &expressions,
                                    const vector<unsigned> flags,
                                    const vector<unsigned> ids,
                                    unsigned int mode) {
    hs_database_t *db;
    hs_compile_error_t *compileErr;
    hs_error_t err;

    Clock clock;
    clock.start();

    err = hs_compile_multi(expressions.data(), flags.data(), ids.data(),
                           expressions.size(), mode, nullptr, &db, &compileErr);

    clock.stop();

    if (err != HS_SUCCESS) {
        if (compileErr->expression < 0) {
            // The error does not refer to a particular expression.
            cerr << "ERROR: " << compileErr->message << endl;
        } else {
            cerr << "ERROR: Pattern '" << expressions[compileErr->expression]
                 << "' failed compilation with error: " << compileErr->message
                 << endl;
        }
        // As the compileErr pointer points to dynamically allocated memory, if
        // we get an error, we must be sure to release it. This is not
        // necessary when no error is detected.
        hs_free_compile_error(compileErr);
        exit(-1);
    }//...}

其中的核心代码是hs_compile_multi的调用,此函数用来编译多个正则表达式,从代码可见除了mode参数,BLOCK和STREAM模式都使用这一API。它的原型是

hs_error_t hs_compile_multi(const char *const * expressions, 
                            const unsigned int * flags, 
                            const unsigned int * ids, 
                            unsigned int elements, 
                            unsigned int mode, 
                            const hs_platform_info_t * platform, 
                            hs_database_t ** db, 
                            hs_compile_error_t ** error)

其中,expressions是多个正则表达式字符串,flags和ids分别是expressions对应的flag和id数组;elements是表达式字符串的个数;其余参数与上一个例子中提到的hs_compile的参数涵义相同。

这里要注意的一个事情是参数ids,它是正则表达式的ID数组。每个表达式都有一个唯一ID,这样命中的时候匹配回调函数可以得到此ID,告诉调用者哪个表达式命中了。如果ids传入NULL,则所有表达式的ID都为0。

2.2 准备匹配临时数据

Benchmark构造函数中,为接下来的匹配分配足够的临时数据空间(scratch space)。这里有一个技巧:1)BLOCK和STREAM模式的匹配只需共用一个scratch;2)这个scratch足够大,方法是调用两次,在第2次调用时hyperscan如果发现空间不够会进行增加。

public:
    Benchmark(const hs_database_t *streaming, const hs_database_t *block)
        : db_streaming(streaming), db_block(block), scratch(nullptr),
          matchCount(0) {
        // Allocate enough scratch space to handle either streaming or block
        // mode, so we only need the one scratch region.
        hs_error_t err = hs_alloc_scratch(db_streaming, &scratch);
        if (err != HS_SUCCESS) {
            cerr << "ERROR: could not allocate scratch space. Exiting." << endl;
            exit(-1);
        }
        // This second call will increase the scratch size if more is required
        // for block mode.
        err = hs_alloc_scratch(db_block, &scratch);
        if (err != HS_SUCCESS) {
            cerr << "ERROR: could not allocate scratch space. Exiting." << endl;
            exit(-1);
        }
    }

2.3 读取数据包、分流

在Benchmark::readStreams方法中,从pcap文件中读取了所有数据包(其实封装必须是ethernet-ipv4-tcp/udp),并根据五元组进行简单分流。主要代码如下

while ((pktData = pcap_next(pcapHandle, &pktHeader)) != nullptr) {
            unsigned int offset = 0, length = 0;
            if (!payloadOffset(pktData, &offset, &length)) {
                continue;
            }

            // Valid TCP or UDP packet
            const struct ip *iphdr = (const struct ip *)(pktData
                    + sizeof(struct ether_header));
            const char *payload = (const char *)pktData + offset;

            size_t id = stream_map.insert(std::make_pair(FiveTuple(iphdr),
                                          stream_map.size())).first->second;

            packets.push_back(string(payload, length));
            stream_ids.push_back(id);
        }

注意,stream_ids这个vector存储了每一个数据包对应的stream id。

2.4 打开流

由于需要用到STREAM模式,所以在匹配前要先将流打开,见Benchmark::openStreams

// Open a Hyperscan stream for each stream in stream_ids
    void openStreams() {
        streams.resize(stream_map.size());
        for (auto &stream : streams) {
            hs_error_t err = hs_open_stream(db_streaming, 0, &stream);
            if (err != HS_SUCCESS) {
                cerr << "ERROR: Unable to open stream. Exiting." << endl;
                exit(-1);
            }
        }
    }

其中,streams的类型是vector<hs_stream_t *>。

2.5 匹配

2.5.1 STREAM模式

在Benchmark::scanStreams中

// Scan each packet (in the ordering given in the PCAP file) through
    // Hyperscan using the streaming interface.
    void scanStreams() {
        for (size_t i = 0; i != packets.size(); ++i) {
            const std::string &pkt = packets[i];
            hs_error_t err = hs_scan_stream(streams[stream_ids[i]],
                                            pkt.c_str(), pkt.length(), 0,
                                            scratch, onMatch, &matchCount);
            if (err != HS_SUCCESS) {
                cerr << "ERROR: Unable to scan packet. Exiting." << endl;
                exit(-1);
            }
        }
    }

hs_scan_stream的原型:

hs_error_t hs_scan_stream(hs_stream_t * id, 
                          const char * data, 
                          unsigned int length, 
                          unsigned int flags, 
                          hs_scratch_t * scratch, 
                          match_event_handler onEvent, 
                          void * ctxt)

其中,id是数据所属的stream对应hs_stream_t指针,这里叫id其实我感觉不太合适; 其余参数与hs_scan相同。

这里调用的streams[stream_ids[i]]已经在上一步打开流中初始化。

2.5.2 BLOCK模式

BLOCK模式比STREAM简单许多,在Benchmark::scanBlock中

// Scan each packet (in the ordering given in the PCAP file) through
    // Hyperscan using the block-mode interface.
    void scanBlock() {
        for (size_t i = 0; i != packets.size(); ++i) {
            const std::string &pkt = packets[i];
            hs_error_t err = hs_scan(db_block, pkt.c_str(), pkt.length(), 0,
                                     scratch, onMatch, &matchCount);
            if (err != HS_SUCCESS) {
                cerr << "ERROR: Unable to scan packet. Exiting." << endl;
                exit(-1);
            }
        }
    }

hs_scan在解读simple中已经说过了,不再赘述。

2.6 清理资源

包括关闭流(hs_close_stream)、释放database等。这里要注意hs_close_stream时仍会进行匹配。

3. STREAM模式总结

STREAM模式的用法比BLOCK模式要复杂一些,这里简单用伪代码总结一下

// N是流的规格,事先已确定好
hs_database_t* db;
hs_stream_t*  steams[N];
hs_scratch_t* tmp;
uint8_t* pkt;

// 1) 编译多个正则表达式
hs_compile_multi(&db, HS_MODE_STREAM);
// 2) 准备scratch
hs_alloc_scratch(db, &tmp);
// 3) 打开流
for(i=0; i<N; i++)
  hs_open_stream(db, &streams[i]);
// 4) 收到数据包,并将其分到指定流
stream_id = classify(pkt);
// 5) 流匹配
hs_scan_stream(streams[stream_id], pkt, &tmp, callBack);
// 6) 清理资源, 注意hs_close_stream仍可能有匹配
for(i=0; i<N; i++)
  hs_close_stream(db, streams[i], &tmp, callBack);hs_free_scrach(tmp);
hs_free_database(db);

可以通过hs_database_size()和hs_stream_size()分别获得database和每条流的stream state的大小。正则表达式的数目和复杂度会影响stream state的大小,随着数目和复杂度的增加,可能会越来越大。在支持上百万条流和复杂规则文件的系统上,stream state的内存耗费可能很大。

4. 编译运行

运行示例前要准备一个pcap文件和一个规则文件,规则文件的格式如

123:/weibo/
456:/[f|F]ile/

每行一个正则表达式,冒号前面是表达式的ID,后面是pcre正则表达式。

以下是编译和运行截图,我用了一个微博流量的pcap,并匹配其中的weibo关键字:

[email protected]:~/hs_demo$ g++ -o pcapscan pcapscan.cc -std=c++11 -lhs -lpcap  
[email protected]:~/hs_demo$ ./pcapscan ptn weibo.pcap 
Pattern file: ptn
Compiling Hyperscan databases with 1 patterns.
Hyperscan streaming mode database compiled in 0.000236959 seconds.
Hyperscan block mode database compiled in 4.8277e-05 seconds.
PCAP input file: weibo.pcap
4 packets in 3 streams, totalling 3641 bytes.
Average packet length: 910 bytes.
Average stream length: 1213 bytes.

Streaming mode Hyperscan database size    : 1000 bytes.
Block mode Hyperscan database size        : 1000 bytes.
Streaming mode Hyperscan stream state size: 25 bytes (per stream).

Streaming mode:

  Total matches: 9
  Match rate:    2.5312 matches/kilobyte
  Throughput (with stream overhead): 2576.33 megabits/sec
  Throughput (no stream overhead):   5444.49 megabits/sec

Block mode:

  Total matches: 9
  Match rate:    2.5312 matches/kilobyte
  Throughput:    16227.30 megabits/sec


WARNING: Input PCAP file is less than 2MB in size.
This test may have been too short to calculate accurate results.
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/hhd1988/article/details/113700068

智能推荐

关于对H264码流的PS的封装的相关代码实现_gb28181_streampackageforh264_对牛乱弹琴的博客-程序员秘密

1、写在开始之前:           最近因为新工作要维护别人留下的GB模块代码,先熟悉了流程,然后也试着封装了下ps流,结果也能通过测试正常预览了,当然,其中开发读文档的头疼,预览花屏,卡帧的事情都有遇到,当时慢慢的看文档,整理逻辑,也就都顺利解决了,下面把大致的一些流程代码贴出来分享下。既然是对接国标,自然少不了通读它的标准文档和相关的RFC文档了!具体的我就不说了,可以用百度goo

Java NIO --- 拉勾教育Java课程 学习笔记_程序员ah鑫的博客-程序员秘密

Java NIO学习1 什么是NIO1.1 标准IO回顾1.2 NIO1.3 NIO的作用1.4 流 和 块1.5 NIO新特性1.5.1 面向流(Stream Oriented)和面向缓冲区(Buff Oriented)1.5.2 阻塞(Blocking IO)和非阻塞(Non-Blocking IO)1.5.3 选择器(Selectors)1.6 NIO的核心组件1 什么是NIO1.1 标准IO回顾什么是IO?IO:Input OutPut(输入 输出)IO技术的作用:解决设备和设备

【C语言】知识点大全-程序员秘密

您好,欢迎浏览知识点大全,希望本页整理的知识点,能帮助你学到更多正确的知识。知识点大全的所有内容均由Zscoon从互联网,教材书籍及试卷试题中收集而来。其中可能存在一些错误或不严谨的地方,如果您发现了错误,欢迎在评论区指正或私信给博主。知识点内容均免费分享,不存在任何收费行为!

python em和web_简单而直接的Python web 框架:web.py_weixin_39999209的博客-程序员秘密

web.py 是一个Python 的web 框架,它简单而且功能强大。web.py 是公开的,无论用于什么用途都是没有限制的。先让大家感受一下web.py 的简单而强大:import weburls = ('/(.*)', 'hello')class hello:def GET(self, name):i = web.input(times=1)if not name: name = 'world...

fatal error LNK1120: 1个无法解析的外部命令_lnk1120无法解析的外部命令_gXh_007的博客-程序员秘密

C++编译时,VS2019出现fatal error LNK1120: 1个无法解析的外部命令错误,查了很多资料,网上说的解决方案如缺少库文件、工程配置等都不行!原来是自己分文件编写时,用了类模板类模板分文件编写,报错: (20210709)main.obj : error LNK2019: 无法解析的外部符号 “public: bool __thiscall OrderList::InitList(void)” ([email protected][email protected]@@QAE_NXZ),函数 “void

i2c知识总结及协议解析_xialianggang1314的博客-程序员秘密

知识总结部分:一. 技术性能:    工作速率有100K和400K两种;    支持多机通讯;    支持多主控模块,但同一时刻只允许有一个主控;          由数据线SDA和时钟SCL构成的串行总线;    每个电路和模块都有唯一的地址;                        每个器件可以使用独立电源二. 基本工作原理:    以启动信号ST

随便推点

简单拦截器日志打印两种实现_FLYHHDD的博客-程序员秘密

简单拦截器日志打印两种实现AOP拦截package com.per.demo.configuration;import com.alibaba.fastjson.JSONObject;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspe...

炫龙T3-pro 9代cpu无csm兼容选项笔记本GPT硬盘纯uefi安装windows7系统方法_hu_shidong的博客-程序员秘密

# 炫龙T3-pro 9代cpu无csm兼容选项笔记本GPT硬盘纯uefi安装windows7系统方法关键词:无csm,9代cpu,笔记本,windows7安装,win7安装,纯uefi,GPT硬盘动机win7现在已不太支持一些新的电脑,但由于工作笔记本需要安装win7系统,所以还是需要按要求来安装,那么首先用我自己的个人电脑安装一下试试,我的是炫龙的t3-pro,算是比较新的电脑,如果他安...

java把在线图片转化流_java 将html转为图片,然后转为base64_灵魂莲华的博客-程序员秘密

将HTML页面转为图片,找了很多方法,发现CSSBox对前端css支持性最好。在解码base64的时候,会出现中文乱码的问题,删除空格和换行即可采用CSSBox(http://cssbox.sourceforge.net/)net.sf.cssboxcssbox4.12HTML转图片,再转base64ImageRenderer render = new ImageRenderer();String...

为什么 SSH 的默认端口是 22 ?_BioIT爱好者的博客-程序员秘密

原文:https://www.ssh.com/ssh/port 作者:Tatu Ylonen ...

md5在线加密解密是不是什么都能解密?为什么我的没有解出来呢?_根据md5能解码出原来的信息吗_哈客部落的博客-程序员秘密

网上有很多md5在线加解密站,熟悉网络安全或者IT技术的朋友可能会比较熟悉。MD5是一种加密技术方法。MD5的全称是Message-digest Algorithm 5,也称为信息摘要算法。主要是用于保证信息传输的完整一致。...

推荐文章

热门文章

相关标签