这篇文章现在有点过时了。看不懂的文字只适用于3.11以下的内核版本。
对于新内核 (>= 3.11)
如果您确定您的代码只能用于 >= 3.11 的内核,您可以将下一个代码用于 input 和 output 数据包:
udp_header = (struct udphdr *)skb_transport_header(skb);
或者更优雅:
udp_header = udp_hdr(skb);
因为ip_rcv()中已经为你设置好了transport header:
skb->transport_header = skb->network_header + iph->ihl*4;
这个改动是由this commit带来的。
对于旧内核 (
传出数据包 (NF_INET_POST_ROUTING)
在这种情况下.transport_header 字段在sk_buffer 中正确设置,因此它指向实际的传输层 标头(UDP/TCP)。所以你可以使用这样的代码:
udp_header = (struct udphdr *)skb_transport_header(skb);
或更好看(但实际上相同):
udp_header = udp_hdr(skb);
传入数据包 (NF_INET_PRE_ROUTING)
这是棘手的部分。
在这种情况下,.transport_header 字段未设置为sk_buffer 结构中的实际 传输层 标头(UDP 或 TCP)(您在 netfilter 挂钩函数中获得)。相反,.transport_header 指向 IP 标头(即网络层标头)。
所以你需要自己计算transport header的地址。为此,您需要跳过 IP 标头(即将 IP 标头长度添加到您的.transport_header 地址)。这就是为什么您可以在文章中看到下一个代码:
udp_header = (struct udphdr *)(skb_transport_header(skb) + 20);
所以20这里只是IP头的长度。
这样可以做得更优雅:
struct iphdr *iph;
struct udphdr *udph;
iph = ip_hdr(skb);
/* If transport header is not set for this kernel version */
if (skb_transport_header(skb) == (unsigned char *)iph)
udph = (unsigned char *)iph + (iph->ihl * 4); /* skip IP header */
else
udph = udp_hdr(skb);
在此代码中,我们使用实际 IP 标头大小(iph->ihl * 4,以字节为单位)而不是幻数 20。
文章中的另一个神奇数字是下一个代码中的17:
if (ip_header->protocol == 17) {
在此代码中,您应该使用IPPROTO_UDP 而不是17:
#include <linux/udp.h>
if (ip_header->protocol == IPPROTO_UDP) {
Netfilter输入/输出包说明
如果您需要一些关于 netfilter 中传入和传出数据包的区别的参考,请参见下图。
详情:
[1]:Some useful code from GitHub
[2]:"Linux Kernel Networking: Implementation and Theory" by Rami Rosen
[3]:This answer may be also useful