Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[理论上的bug] Kindling 不能正确处理TCP 长连接的两个场景 #435

Open
LambertZhaglog opened this issue Jan 11, 2023 · 17 comments
Labels
bug Something isn't working

Comments

@LambertZhaglog
Copy link
Contributor

Describe the bug

参见这里 对connection, stream, message 的定义。在一个TCP connection上,Kindling不能正确处理下面两种类型的通信场景

case1: 两个流的response message 逆序

case2: client发起的流包裹 server 发起的流

图示如下
kindling-issue-response-interleaving
case1: Client 连续向Server发送两个请求request1 和 request2,Server先响应了response2,后响应response1。 按照当前 Network.ConsumeEvent的处理逻辑:request2 报文会被merge到request1的尾部(即request2被隐藏),response1 会被丢弃,request1 和response2会被错误的配对在一起,进行 NetworkAnalyzer.distributeTraceMetric分析

kindling-issue-stream-inline

case2: Client 首先向Server发送request1,Server收到request1 没有立即响应,而是向Client发送了request2, Client响应了response2 后,Server才响应response1。 按照当前 Network.ConsumeEvent 的处理逻辑:request1 和response2 会被配对,进行NetworkAnalyzer.distributeTraceMetric分析,response2 和response1 也会被配对,进行NetworkAnalyzer.distributeTraceMetric分析。错误的配对,必然导致解析失败

Additional context

这两个case,是我用kindling解析公司自研协议观察到的,我设计了对这个问题的解决方案,正在编码实现。本周我会把解决方案提交成proposal

@LambertZhaglog LambertZhaglog added the bug Something isn't working label Jan 11, 2023
@dxsup
Copy link
Member

dxsup commented Jan 11, 2023

请问贵司的自研协议是类似于HTTP/2的流式协议吗?如果是的话,对于这两个场景Kindling必须拿全每个系统调用的data部分才能够解析识别出不同的stream;而目前考虑到性能影响,默认只获取了200bytes。可以在这里先简单描述一下你的方案吗?

@LambertZhaglog
Copy link
Contributor Author

自研协议哪些特征

  1. 自研协议没有frame的概念,即message 的header 和 body不被划分为多个frame传输
  2. 每个message 都有 stream ID
  3. 目前抓包来看,message 和 syscall的关系如下
    1. 几乎全部,一个message 对应一个syscall
    2. 存在一个message需要经过两次syscall 才接收完成的情况
    3. 不存在多个message 被合并到一个syscall的情况
  4. 自研协议和http/2 一样,是binary protocol,不是 ASCII protocol。参见

还需要我补充自研协议的哪些信息?

场景根因

对于这两个场景Kindling必须拿全每个系统调用的data部分才能够解析识别出不同的stream

我不认为,kindling不能正确处理这两个场景是因为暂定不拿全data。 我认为是何时进行 message pairing(报文配对)的问题,如何做报文配对的问题。 我认为 issue277 也是报文配对时机不对导致的。

描述方案

NetworkAnalyzer 为了把KindlingEvent 转化为DataGroup,我认为是做了三件事:报文配对,协议识别,协议解析。协议识别完全依赖协议解析,方案暂时不考虑协议识别。当前的做法是,先报文配对,后协议解析。我的方案的核心是先协议解析,后根据解析结果进行报文配对去生成一个request-response的相关指标。协议解析本质上也是对request 和response message 分别的解析,message解析并不依赖报文配对(不认同,我们可以讨论)。但先报文配对,在如上的场景下,就造成若干个message 非法,但其实它们都合法,是报文配对错了。

我设想的方案中是需要拿全data的。有些协议可能会出现多个message 合并在一个syscall 被捕获,拿全data对kindling正确处理这种协议至关重要。同时,kindling不应该对用户态程序如何做Network syscall 做假设。

社区可能会担心,采用我描述的方案后,如何做协议识别。我能给出一个不太成熟的方案。另外我认为,kindling 需要对协议识别做多大程度的保证,是一个需要思考和讨论的问题。

性能影响

我想测试一下:解除目前data只能200字节长的限制会对kindling 的性能造成多大影响。 我想问,我应该对 falco-lib, kindling-probe, kindling-collector 的源代码做哪些改动?我主观认为解除限制,性能影响不会很大。因为解除限制,增加的开销只在于增大了event data 在各个组件间传递时的内存拷贝,中间如果是指针传递,内存拷贝其实是免了的。同时解除限制完全不会影响NetworkAnalyzer之后的分析流程。出于性能考虑,是因为已经观察到拿全数据带来很大性能开销吗?或有什么样经验知识?希望提供数据或资料参考。

@dxsup
Copy link
Member

dxsup commented Jan 12, 2023

根据你对特征的描述,我理解这个协议不是类似HTTP/2的通信模型,所以我上面的假设不成立,这两个场景不是因为拿不全data导致的。

但是根据你在另一个issue #436 中的描述:

通信时,每个stream 有一个stream ID(简记为sid),严格遵守一问一答的方式,即client必须收到前一个stream 的response,才会发出后一个stream的request。

在 case1 中,对于同一个TCP Stream,Client 是如何连续向Server发送两个请求request1 和 request2的?

@LambertZhaglog
Copy link
Contributor Author

#436 和 这个issue确实是说的同一个协议。#436 是一个测试场景,强制让协议工作在一问一答模式下的。这个协议是允许case1 场景存在的,case1 场景的存在是这个协议的一个优势。

@dxsup
Copy link
Member

dxsup commented Jan 12, 2023

了解了。

关于性能影响,目前从probe到collector存在事件的内存拷贝,这部分能否全部转换为指针传递还要进一步研究。另外,事件从底层ring buffer中拿出来时也存在拷贝,所以falco/libs设置了snaplen这个参数。

性能影响有两方面,一个是CPU会升高,一个是内存占用会升高(因为要缓存系统调用中的data),我们之前测试过获取5000bytes相比于获取80bytes,CPU会有升高一些,这也是为什么falco/libs会设置这个参数的原因。

@LambertZhaglog
Copy link
Contributor Author

好奇问一下,社区认为流式协议的特征是什么?为什么我所说的自研协议不是流式协议?

@dxsup
Copy link
Member

dxsup commented Jan 13, 2023

首先明确一个概念,所谓“流式协议”并不是一个标准的术语(有人会说TCP是流式协议,但更准确的说法是面向流的协议),Kindling使用这个词主要用来描述基于HTTP/2实现的协议。HTTP/2支持多路复用,可以将请求/响应分解为frames在同一个连接上乱序发送,这种现象反映在系统调用上就是,在一个系统调用中存在多个请求或响应的字节。对于这种模式的通信协议,Kindling目前无法解析,如之前所说,要解析这种协议的前提是获取完整的报文字节。

回到问题本身:

社区认为流式协议的特征是什么?

我并不敢给“流式协议”这个词下定义,所以前面讨论Magic协议时都没有直接用这个词,而是带上了HTTP/2:

请问贵司的自研协议是类似于HTTP/2的流式协议吗?
我理解这个协议不是类似HTTP/2的通信模型

这里的关键点是,Magic协议是否拥有类似于HTTP/2的分拆frame和乱序发送的特性,这个特性会导致在一个系统调用中存在多个请求或响应的字节,并且他们还可能是乱序的。

为什么我所说的自研协议不是流式协议?

如前面所述,准确的说,Magic协议不是类似于HTTP/2的通信模型,因为从你描述的特征来看,Magic协议不会出现在一个系统调用中存在多个请求或响应的字节的问题。

综上,关于“Magic协议是否是流式协议”这个问题本身其实并不重要,重要的是Magic协议中是否存在一个系统调用中存在多个请求/响应,这决定了目前Kindling的协议解析模型是否适用。

@LambertZhaglog

This comment was marked as outdated.

@LambertZhaglog
Copy link
Contributor Author

Magic协议允许每个message出现在同一个系统调用中,而且实际应用场景“这种组包”频繁发生

@dxsup
Copy link
Member

dxsup commented Jan 16, 2023

如果是这样的话,要想基于报文内容来解析协议,就不得不获取完整的报文,这可能对性能有较大影响。在获取完整报文的基础上还需要调整解析代码的模型。

另一个方案是基于uprobe来实现该协议的识别和关联。请问这个协议都有哪些编程语言在使用?

@LambertZhaglog
Copy link
Contributor Author

Rust,Python,C++

@dxsup
Copy link
Member

dxsup commented Jan 16, 2023

uprobe比较麻烦的地方在于需要针对不同的编程语言不同的API做适配,然后再对获取到的事件做关联;由于添加uprobe后会频繁触发上下文切换,可能会对用户程序造成影响,因此uprobe只在确认“解析报文无法识别协议“时才会被使用。

还有一个问题要确认,Magic协议是否会像HTTP/2一样在连接建立时建立Header编解码表?这个表的存在导致HTTP/2协议几乎不能用“解析报文”的方式来识别,因为在探针启动时如果连接已经建立,则探针无法获取到这个表,也就无法解析后续请求。

根据你之前的描述,应该不存在这个问题,所以我建议你可以尝试将获取报文大小的环境变量SNAPLEN调整到最大65000,观察一下是否获取到了完整的协议内容和性能表现,如果可以接受的话,我们再一起修改解析模型来适配你的场景。

@hocktea214
Copy link
Collaborator

基于Case1 Kindling本身是支持的,DNS解析就是基于该场景,详见parseMultipleRequests():network_analyzer.go
基于Case2 Kindling也考虑过方案,基于FD与协议强绑定,不同协议采用不同的模型解析。

  • Kindling冷启动接收消息试着基于不同协议识别该FD数据,匹配成功后进行协议绑定,后续数据基于该协议进行解析
  • 考虑到数据拆包情况,第一个包携带可解析的重要信息,长度、标识、关键指标;后续包只是补充数据,由于获取包时限定长度截断导致第二个、第三个、第N个包无法进行合并然后统一解析。
  • 解析出的request / response放置在相应队列中等待新response/request进行匹配

该方案的局限性

  • FD必须只有1个协议,不能复用
  • 由于Kindling探针包截断的局限性,第一个数据包需携带长度信息,用于拼接拆包数据

由于HTTP2协议编码问题导致冷启动的Kindling无法获取相应数据进行解析采用uprobe分析,网关复用FD,不是所有协议都有长度等因素,Case2的方案也就暂时没有启用

@LambertZhaglog
Copy link
Contributor Author

@hocktea214
case1: ConsumeEvent接收序列 request1, request2, response2, response1 之后,报文配对方法不是

request2 报文会被merge到request1的尾部(即request2被隐藏),response1 会被丢弃,request1 和response2会被错误的配对在一起

我搞错了,应该是,request2 报文会被merge到request1的尾部,response1 报文会被merge到response2的尾部。所以 Kindling 解析DNS确实正确的处理了该场景。

想到一种新场景(记为case3),和case1类似,但Kindling解析DNS时是无法正确处理的。可能DNS不会遇到该场景,我现在也无法确信Magic是否存在该场景。该场景ConsumeEvent接收的序列是,request1, request2, response1, request3, response2, response3。

Kindling如果允许前一次调用distributeTraceMetric 时将未完成配对的request保留下来,供下一次调用distributeTraceMetric时response与其配对,可以解决这个问题


您介绍的,针对case2的方案

解析出的request / response放置在相应队列中等待新response/request进行匹配

这需要改变目前的报文配对规则吧? 您能更详细描述针对case2的方案吗?

@LambertZhaglog
Copy link
Contributor Author

@dxsup
看起来如果换用uprobe,很大开发量,而且要求Kindling和Magic的实现版本绑定。

Magic协议没有Header编解码表。

我现在的考虑是,允许不拿全数据,丢弃因此受到影响的request/response message(不让他们生成DataGroup)。Kindling 最后输出的指标比真实值小一点,我想也是可以接收的。

@dxsup
Copy link
Member

dxsup commented Jan 16, 2023

@dxsup 看起来如果换用uprobe,很大开发量,而且要求Kindling和Magic的实现版本绑定。

Magic协议没有Header编解码表。

我现在的考虑是,允许不拿全数据,丢弃因此受到影响的request/response message(不让他们生成DataGroup)。Kindling 最后输出的指标比真实值小一点,我想也是可以接收的。

这个要测试一下看看究竟有多大的差距,如果能从中发现一些概率关系就可以这么做。但使用这个指标时必须要明确标明是近似值,否则如果基于错误假设实现后续功能会出现连环问题。

@hocktea214
Copy link
Collaborator

hocktea214 commented Feb 20, 2023

@LambertZhaglog 现已基于流式协议提供一版方案
协议开发流程.pdf

请求先基于连接缓存,当该连接为第一次建立,通过Check() API识别是否流式协议,并匹配该协议;否则采用原MessagePair模型。

当识别为流式协议后,以后所有请求/响应都实时解析分析

  • ParseHead()获取长度、streamId、是否请求(双工场景)
  • 通过streamId获取请求
    • 存在oneway场景,此时如果接收到的是request,则将缓存中的request先发送,再解析接收的request
  • 基于长度判断是否存在粘包场景(一个系统调用发送多个请求/响应报文)
    • 存在粘包则基于长度拆包并解析报文
    • 长度不足则等待后续报文
    • 长度匹配则解析该完整报文
  • 由于流式协议响应报文缺失请求报文的API Key等信息,此时在解析前会先MergeRequest()将请求解析的属性合并到响应中
  • ParsePayload()解析报文
    • 请求/响应都存在则返回请求响应
    • 只有请求则缓存解析出的请求属性
    • 只有响应则丢弃该请求(存在探针冷启动时只获取到响应数据)

此外,对于双工场景,同时存在双向通信数据,需实现IsRequest() 和 IsReverse()方法即可校准该连接数据的方向

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants