1

我正在尝试从 a 中提取数据struct sk_buff,但没有收到我期望的输出。有问题的帧是 34 字节;一个 14 字节的以太网报头包裹着一个 8 字节(实验协议)报头:

struct monitoring_hdr {
    u8      version;
    u8      type;
    u8      reserved;
    u8      haddr_len;

    u32     clock;
} __packed;

在此标头之后,有两个可变长度的硬件地址(它们的长度由haddr_len上面的字段决定)。在此处的示例中,它们都是 6 个字节长。

以下代码正确提取了标头(结构),但没有提取后面的两个 MAC 地址。

发送方:

    ...
    skb = alloc_skb(mtu, GFP_ATOMIC);
    if (unlikely(!skb))
            return;
    skb_reserve(skb, ll_hlen);
    skb_reset_network_header(skb);
    nwp = (struct monitoring_hdr *)skb_put(skb, hdr_len);
    /* ... Set up fields in struct monitoring_hdr ... */
    memcpy(skb_put(skb, dev->addr_len), src, dev->addr_len);
    memcpy(skb_put(skb, dev->addr_len), dst, dev->addr_len);
    ...

接收方:

   ...
   skb_reset_network_header(skb);
   nwp = (struct monitoring_hdr *)skb_network_header(skb);

   src = skb_pull(skb, nwp->haddr_len);
   dst = skb_pull(skb, nwp->haddr_len);
   ...

预期输出:

我使用 tcpdump 来捕获线路上的相关数据包,并看到了这一点(它实际上被发送者的 NIC 填充到 60 个字节,我已经省略了):

0000 | 00 90 f5 c6 44 5b 00 0e  c6 89 04 2f c0 df 01 03
0010 | 00 06 d0 ba 8c 88 00 0e  c6 89 04 2f 00 90 f5 c6
0020 | 44 5b

前 14 个字节是以太网报头。以下 8 个字节(以 开头01和结尾88)应该是放入 的字节struct monitoring_hdr,它可以正确执行。然后,我期望找到以下 MAC 地址:

src = 00 0e c6 89 04 2f
dst = 00 90 f5 c6 44 5b

实际输出:

但是,我收到的数据向左移动了两个字节:

src = 8c 88 00 0e c6 89
dst = 04 2f 00 90 f5 c6

任何人都可以在上面的代码中看到逻辑缺陷吗?还是有更好的方法来做到这一点?我也尝试skb_pull过代替skb_network_header接收方,但这导致内核恐慌。

提前感谢您的帮助。

解决方案

指向 中数据的第一个字节的指针没有像它应该sk_buff指向的那样被指向。src我最终使用了以下内容:

   ...
   skb_reset_network_header(skb);
   nwp = (struct monitoring_hdr *)skb_network_header(skb);
   skb_pull(skb, offsetof(struct monitoring_hdr, haddrs_begin));

   src = skb->data;
   dst = skb_pull(skb, nwp->haddr_len);
   ...
4

1 回答 1

3

查看skbuff.h 标头,您使用的函数如下所示:

static inline void skb_reset_network_header(struct sk_buff *skb)
{
        skb->network_header = skb->data - skb->head;
}

static inline unsigned char *skb_network_header(const struct sk_buff *skb)
{
        return skb->head + skb->network_header;
}

extern unsigned char *skb_pull(struct sk_buff *skb, unsigned int len);
static inline unsigned char *__skb_pull(struct sk_buff *skb, unsigned int len)
{
        skb->len -= len;
        BUG_ON(skb->len < skb->data_len);
        return skb->data += len;
}

因此,首先,我会尝试打印出来skb->dataskb->head确保它们引用了您期望它们引用的数据包部分。由于您在此处使用自定义协议,因此标头处理代码中可能存在错误,导致skb->data设置不正确。

此外,查看sky_network_headerand的定义skb_pull让我觉得您可能错误地使用了它们。第一个 6 字节地址不应该位于指向的返回值的位置skb_network_header()吗?看起来该函数将标头块的长度添加到缓冲区的头部,这应该会产生一个指向您的第一个数据值的指针。

同样,它看起来像skb_pull()添加了您传入的字段的长度并将指针返回到下一个字节。所以你可能想要更像这样的东西:

src = skb_network_header(skb);
dst = skb_pull(skb, nwp->haddr_len);

我希望这会有所帮助。很抱歉,这不是一个确切的答案。

于 2012-12-02T20:02:33.543 回答