流解析器 (strparser)

简介

流解析器 (strparser) 是一个实用工具,用于解析在数据流上运行的应用层协议的消息。流解析器与内核中的上层协同工作,为应用层消息提供内核支持。例如,内核连接多路复用器 (KCM) 使用流解析器通过 BPF 程序解析消息。

strparser 在两种模式下工作:接收回调或通用模式。

在接收回调模式下,strparser 从 TCP 套接字的 data_ready 回调中调用。消息在套接字上接收时被解析并传递。

在通用模式下,一系列 skb 从外部源馈送到 strparser。消息在处理序列时被解析和传递。此模式允许将 strparser 应用于任意数据流。

接口

API 包括一个上下文结构、一组回调函数、实用函数以及用于接收回调模式的 data_ready 函数。回调包括一个 parse_msg 函数,该函数被调用以执行解析(例如,在 KCM 的情况下进行 BPF 解析),以及一个 rcv_msg 函数,该函数在完整消息完成时被调用。

函数

strp_init(struct strparser *strp, struct sock *sk,
        const struct strp_callbacks *cb)

用于初始化流解析器的调用。 strp 是由上层分配的 strparser 类型的结构。 sk 是与流解析器关联的 TCP 套接字,用于接收回调模式;在通用模式下,此值设置为 NULL。回调由流解析器调用(回调如下所列)。

void strp_pause(struct strparser *strp)

临时暂停流解析器。消息解析被暂停,并且没有新的消息传递到上层。

void strp_unpause(struct strparser *strp)

取消暂停已暂停的流解析器。

void strp_stop(struct strparser *strp);

strp_stop 被调用以完全停止流解析器的操作。当流解析器遇到错误时,会在内部调用它,并且上层也会调用它以停止解析操作。

void strp_done(struct strparser *strp);

strp_done 被调用以释放流解析器实例所持有的任何资源。这必须在流处理器停止后调用。

int strp_process(struct strparser *strp, struct sk_buff *orig_skb,
                 unsigned int orig_offset, size_t orig_len,
                 size_t max_msg_size, long timeo)

strp_process 在通用模式下调用,以使流解析器解析 sk_buff。返回已处理的字节数或负错误号。请注意,strp_process 不会消耗 sk_buff。 max_msg_size 是流解析器将解析的最大大小。 timeo 是完成消息的超时时间。

void strp_data_ready(struct strparser *strp);

当较低套接字上有数据准备好供 strparser 处理时,上层调用 strp_tcp_data_ready。这应该从在套接字上设置的 data_ready 回调中调用。请注意,最大消息大小是接收套接字缓冲区的限制,而消息超时是套接字的接收超时。

void strp_check_rcv(struct strparser *strp);

strp_check_rcv 被调用以检查套接字上的新消息。这通常在流解析器实例初始化时或在 strp_unpause 之后调用。

回调

有六个回调

int (*parse_msg)(struct strparser *strp, struct sk_buff *skb);

parse_msg 被调用以确定流中下一个消息的长度。上层必须实现此函数。它应该将 sk_buff 解析为包含流中下一个应用层消息的标头。

输入 skb 中的 skb->cb 是一个 struct strp_msg。只有 offset 字段与 parse_msg 相关,并给出消息在 skb 中开始的偏移量。

此函数的返回值是

>0

指示成功解析的消息的长度

0

指示必须接收更多数据才能解析消息

-ESTRPIPE

内核不应处理当前消息,将套接字的控制权返回给用户空间,用户空间可以继续读取消息本身

其他 < 0

解析错误,将控制权返回给用户空间,假设同步丢失且流不可恢复(应用程序期望关闭 TCP 套接字)

如果返回错误(返回值小于零)并且解析器处于接收回调模式,则它将在 TCP 套接字上设置错误并将其唤醒。如果 parse_msg 返回 -ESTRPIPE 并且流解析器之前读取了当前消息的一些字节,那么在这种情况下,附加套接字上设置的错误是 ENODATA,因为流是不可恢复的。

void (*lock)(struct strparser *strp)

当 strparser 执行异步操作(例如处理超时)时,调用 lock 回调来锁定 strp 结构。在接收回调模式下,默认函数是锁定相关套接字的 lock_sock。在通用模式下,必须正确设置回调。

void (*unlock)(struct strparser *strp)

调用 unlock 回调以释放 lock 回调获取的锁。在接收回调模式下,默认函数是释放相关套接字的 release_sock。在通用模式下,必须正确设置回调。

void (*rcv_msg)(struct strparser *strp, struct sk_buff *skb);

rcv_msg 在接收到完整消息并将其排队时调用。被调用者必须使用 sk_buff;它可以调用 strp_pause 以防止在 rcv_msg 中接收任何进一步的消息(参见上面的 strp_pause)。必须设置此回调。

输入 skb 中的 skb->cb 是一个 struct strp_msg。此结构包含两个字段:offset 和 full_len。Offset 是消息在 skb 中开始的位置,而 full_len 是消息的长度。skb->len - offset 可能大于 full_len,因为 strparser 不会修剪 skb。

   int (*read_sock_done)(struct strparser *strp, int err);

read_sock_done is called when the stream parser is done reading
the TCP socket in receive callback mode. The stream parser may
read multiple messages in a loop and this function allows cleanup
to occur when exiting the loop. If the callback is not set (NULL
in strp_init) a default function is used.

::

   void (*abort_parser)(struct strparser *strp, int err);

This function is called when stream parser encounters an error
in parsing. The default function stops the stream parser and
sets the error in the socket if the parser is in receive callback
mode. The default function can be changed by setting the callback
to non-NULL in strp_init.

统计信息

为每个流解析器实例保留各种计数器。这些计数器位于 strp_stats 结构中。 strp_aggr_stats 是一个方便的结构,用于累积多个流解析器实例的统计信息。 save_strp_stats 和 aggregate_strp_stats 是用于保存和聚合统计信息的辅助函数。

消息组装限制

流解析器提供了限制消息组装所消耗资源的机制。

当新消息开始组装时,会设置一个计时器。在接收回调模式下,消息超时时间取自相关 TCP 套接字的 rcvtime。在通用模式下,超时时间作为 strp_process 中的参数传递。如果计时器在组装完成之前触发,则流解析器将中止,并且如果在接收回调模式下,则在 TCP 套接字上设置 ETIMEDOUT 错误。

在接收回调模式下,消息长度限制为相关 TCP 套接字的接收缓冲区大小。如果 parse_msg 返回的长度大于套接字缓冲区大小,则流解析器将中止,并在 TCP 套接字上设置 EMSGSIZE 错误。请注意,这使得具有流解析器的套接字的接收 skbuff 的最大大小为 TCP 套接字的 2*sk_rcvbuf。

在通用模式下,消息长度限制作为参数传递给 strp_process。

作者

Tom Herbert (tom@quantonium.net)