netfilter 钩子没有检索完整的数据包

netfilter hook is not retrieving complete packet

我正在编写一个 netfilter 模块,它可以深入检查数据包。然而,在测试过程中,我发现 netfilter 模块没有接收到完整的数据包。

为了验证这一点,我编写了以下代码来转储在端口 80 上检索到的数据包并将结果写入 dmesg 缓冲区:

const struct iphdr *ip_header = ip_hdr(skb);
if (ip_header->protocol == IPPROTO_TCP)
{
    const struct tcphdr *tcp_header = tcp_hdr(skb);
    if (ntohs(tcp_header->dest) != 80)
    {
        return NF_ACCEPT;
    }

    buff = (char *)kzalloc(skb->len * 10, GFP_KERNEL);
    if (buff != NULL)
    {
        int pos = 0, i = 0;
        for (i = 0; i < skb->len; i ++)
        {
            pos += sprintf(buff + pos, "%02X", skb->data[i] & 0xFF);
        }

        pr_info("(%pI4):%d --> (%pI4):%d, len=%d, data=%s\n",
            &ip_header->saddr,
            ntohs(tcp_header->source),
            &ip_header->daddr,
            ntohs(tcp_header->dest),
            skb->len,
            buff
        );
        kfree (buff);
    }
}

在虚拟机 运行 本地,我可以检索完整的 HTTP 请求;在阿里云和其他一些基于 OpenStack 的 VPS 提供商上,数据包在中间被切断。

为了验证这一点,我在另一个 VPS 上执行 curl http://VPS_IP,我在 dmesg 缓冲区中得到以下输出:

[ 1163.370483] (XXXX):5007 --> (XXXX):80, len=237, data=451600ED000040003106E3983D87A950AC11D273138F00505A468086B44CE19E80180804269300000101080A1D07500A000D2D90474554202F20485454502F312E310D0A486F73743A2033392E3130372E32342E37370D0A4163636570743A202A2F2A0D0A557365722D4167656E743A204D012000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000001E798090F5FFFF8C0000007B00000000E0678090F5FFFF823000003E00000040AE798090F5FFFF8C0000003E000000000000000000000000000000000000000000000000000000000000

解码后的结果是这样的

这很奇怪,User-Agent: M 之后的所有内容都是 "gone" 或归零。虽然skb->len是237,但是少了一半包。

有什么想法吗? PRE_ROUTING 和 LOCAL_IN 都试过了,没有变化。

看来有时您得到的是线性 skb,有时您的 skb 不是线性的。在后一种情况下,您没有读取 skb 的完整数据内容。

如果skb->data_len为零,那么你的skb是线性的,skb的完整数据内容在skb->data中。如果 skb->data_len 不为零,则您的 skb 不是线性的,并且 skb->data 仅包含数据的第一(线性)部分。该区域的长度为skb->len - skb->data_lenskb_headlen() 辅助函数计算方便。 skb_is_nonlinear() 辅助函数告诉 skb 是否是线性的。

其余数据可以按此顺序放在分页片段和skb 片段中。

skb_shinfo(skb)->nr_frags 告诉分页片段的数量。每个分页片段由结构数组 skb_shinfo(skb)->frags[0..skb_shinfo(skb)->nr_frags] 中的数据结构描述。 skb_frag_size()skb_frag_address() 辅助函数有助于处理这些数据。它们接受描述分页片段的结构地址。根据您的内核版本,还有其他有用的辅助函数。

如果分页片段中的数据总大小小于skb->data_len,则其余数据在skb片段中。它是在 skb_shinfo(skb)->frag_list 处附加到此 skb 的 skb 列表(请参阅内核中的 skb_walk_frags())。

请注意,线性部分可能没有数据and/or分页片段中可能没有数据。您只需要按照刚刚描述的顺序逐条处理数据即可。