首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >网络流量处理中的协议解析七: Protolens

网络流量处理中的协议解析七: Protolens

原创
作者头像
北瓜
修改2025-05-23 16:36:01
修改2025-05-23 16:36:01
1750
举报

实现

综合前面几篇文章,流重组+协议解码的所有环节都已经没有问题。可以据此实现一个完整的协议解析库:Protolens

我们把这个库限定在这样的使用场景之下:

  1. 作为一个库,被通常的多线程流量处理引擎调用。
  2. 但库本身没有必要跨线程使用,也就是说流量处理引擎的每个线程都有自己的协议解析库的实例。
  3. 每个五元组会话,也就是通常所说的流节点都有一个解码器。解码器针对每条连接解码。
  4. 解码器本身不具备协议识别能力。协议识别能力由流量处理引擎提供。协议解析和协议识别看似很接近,但这两个功能如果搅合在一起,显然是个错误的思路。
  5. 解码器的输入是数据包,输出是解码后的协议数据。
  6. 解码器通过回调函数把协议数据返回给用户。

此外,作为一个库,应该尽量利用用户程序的功能,不要重复做功。比如对数据包的解码(ip地址,tcp,udp,端口等),库内部不应该重复做。既然调用者是流量处理引擎,那么流量处理引擎应该早已具备这些功能。库内部如果再做一遍,就会造成重复做功,浪费性能。为此,库可以设置一个传入数据包的trait,要求调用者实现这个trait。在库内部通过这个trait获取数据包的信息即可。

使用方式

Protolens用于处理数据包,tcp流重组,协议解析,协议还原的场景。作为一个库,通常用在网络安全监控,网络流量分析,网络流量还原等引擎当中。

流量引擎通常具有多个线程,每个线程都有自己的流表。每个流节点就是一个五元组。protolens基于这种架构,并不能跨线程使用。

每个线程都应该初始化一个Protolens。在你的流表为一个连接新建一个节点的时候,应该为这个连接新建一个task。

为了获得结果,你需要为感兴趣的每个协议的每个字段设置回调函数。比如 protolens.set_cb_smtp_user(user_callback)设置之后,smtp的user字段就会通过user_callback被回调。

此后,这个连接每到来一个数据包,都要通过run方法,将这个数据包加入到这个task当中。

但protolens,task内部并没有协议识别的能力。此时虽然数据包被传入了task,但是task内部没有开始解码。它会缓存一定数量的数据包,默认是128个。所以你最好在超出缓存的数据包之前,通过set_task_parser告诉task这条连接是什么协议。此后task就开始解码,并通过回调函数把还原内容返回给你。

protolens会被同时编译为c语言可调用的so。使用过程和rust类似。

具体使用请参考rust_example目录和c_example目录。更详细的回调函数用法,可以参考smtp.rs中的测试用例。

你可以通过回调函数得到协议字段,比如smtp的user,邮件内容,http的头字段,请求行,body等。当你在回调函数中得到这些数据等时候,他们是对内部数据等引用。所以,如果你可以在此时立即处理。但如果要后续继续使用,则需要copy一份,放在你指定的地方。你不能把引用保留到外部。rust程序会阻止你这么作,但c程序中作为指针,如果你只把指针保留到后续过程,会指向错误的地方。

如果你想获得原始的tcp流,也有对应的回调函数。此时你的到是一段一段的原始字节。但是经过重组之后的连续的流。同时有对应的序列号。

假设你需要审计协议字段,比如判断http的url是否符合要求。你可以注册对应的回调函数。在函数中,做出判断,或者保存流节点上,供后续模块判断。这是最直接的使用方式。

以上只能看到url,host等独立的协议字段。假设你有这样的要求:在原始tcp流中定位url的位置。因为你还想找到url后面,前面有什么东西。你需要这样做:

通过原始tcp流的回调函数,你可以得到原始的tcp流和seq序列号。copy到你维护的一个buff中。通过url回调函数,的到url和对应的seq。此时你就可以在buff中根据seq确定url的位置。这样,就可以在一个连续的buff空间中处理诸如:url后面有什么内容,前面有什么内容之类的需要。

而且你可以根据seq来取舍buff中的数据。比如你只需要处理url后面的数据,那么你可以根据url的seq,从buff中删除前面的数据。这样,你就可以在一个连续的buff空间中处理url后面的数据。

性能

Strem trait

最开始的版本,为内部的重组结构实现了Stream trait。简单明了,但其next方法是逐个字节返回,虽然性能可以足够达到常规标准。但逐字节处理,相当于每个字节都要调用一次next函数,显然是没必要的。

于是,放弃了Stream trait。在每个读取方法中实现异步执行。这样,就可以在读取重组数据的时候在一次函数调用中在一个buff中一次完成遍历。大大加快了速度。

Packet trait

TCP数据包会乱序,为了重组,就需要缓存数据包。所以Protolens需要获得传入packet当所有权。那么当数据包在内部保存的时候,就需要写入数据。

如果写入的是完整的数据包,在高性能流量处理引擎中,相当于每个数据包都要copy一份。这显然是不合理的。所以最初Protolens在内部实现了一个包装结构。用来只写入指针。但实际上这部分是不必要的。因为通常的流量处理引擎中,肯定会为数据包实现高性能的clone属性,和包装结构。否则流量处理引擎本身也没办法高效处理数据包。那么,Protolens完全可以直接接收用户的Packet。在内部直接视作完整的Packet写入。如果用户实现了高效的包装,则写入的是指针。如果用户没有实现,则写入的就是完整的数据包。把这部分功能依赖用户的实现。避免冗余。

mmchr

在buff中查找一行,一段数据。mmchr会带来明显的性能提升。buff越大效果越明显。

内存池

在实际测试过程中发现,Protolens中的内存池分配对性能的消耗占比并不大。原来用定长数组实现了内存池,为了简化代码,被去掉了。重新使用简单的Vec,只需要让Vec不动态增长即可。

测试结果

以下是测试结果,注意linux的结果是很老旧的cpu,并不是在当前主流高性能cpu上的测试结果。

其中new_task 为单纯新建解码器,不包含解码过程。因为解码过程是按行读取,所以用readline系列单独测试读取一行的性能,这种方式最能代表http smtp类协议的解码性能。每行25个字节,一共100个包。readline100代表每个包100个字节,readline500代表每个包500个字节。readline100_new_task代表新建解码器+解码过程。http,smtp等为实际的pcap数据包。但smtp和pop3最具代表性,因为这两个测试用例的pcap中完全是逐行构造的。其余的有按size读取,所以更快。统计的时候以字节为单位,没有计算数据包头部仅计算数据包的载荷。

测试项目

mamini m4

linux

linux jemalloc

new_task

3.1871 Melem/s

1.4949 Melem/s

2.6928 Melem/s

readline100

1.0737 GiB/s

110.24 MiB/s

223.94 MiB/s

readline100_new_task

1.0412 GiB/s

108.03 MiB/s

219.07 MiB/s

readline500

1.8520 GiB/s

333.28 MiB/s

489.13 MiB/s

readline500_new_task

1.8219 GiB/s

328.57 MiB/s

479.83 MiB/s

readline1000

1.9800 GiB/s

455.42 MiB/s

578.43 MiB/s

readline1000_new_task

1.9585 GiB/s

443.52 MiB/s

574.97 MiB/s

http

1.7723 GiB/s

575.57 MiB/s

560.65 MiB/s

http_new_task

1.6484 GiB/s

532.36 MiB/s

524.03 MiB/s

smtp

2.6351 GiB/s

941.07 MiB/s

831.52 MiB/s

smtp_new_task

2.4620 GiB/s

859.07 MiB/s

793.54 MiB/s

pop3

1.8620 GiB/s

682.17 MiB/s

579.70 MiB/s

pop3_new_task

1.8041 GiB/s

648.92 MiB/s

575.87 MiB/s

imap

5.0228 GiB/s

1.6325 GiB/s

1.2515 GiB/s

imap_new_task

4.9488 GiB/s

1.5919 GiB/s

1.2562 GiB/s

sip (udp)

2.2227 GiB/s

684.06 MiB/s

679.15 MiB/s

sip_new_task (udp)

2.1643 GiB/s

659.30 MiB/s

686.12 MiB/s

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 实现
  • 使用方式
  • 性能
    • Strem trait
    • Packet trait
    • mmchr
    • 内存池
    • 测试结果
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档