流解析器 (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。在 parse_msg 中只有 offset 字段是相关的,它提供了消息在 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)(struct strparser *strp, read_descriptor_t *desc,
             sk_read_actor_t recv_actor);

如果提供了 read_sock 回调,strparser 将使用它而不是 sock->ops->read_sock。

   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)