linux 内核数据包处理中的一些坑和建议

1、获取IP头部

iph = ip_hdr(skb);

struct sk_buff {

......

sk_buff_data_t transport_header; /* Transport layer header */

sk_buff_data_t network_header; /* Network layer header */

sk_buff_data_t mac_header; /* Link layer header */

......

}

1)__netif_receive_skb()在进入三层处理前就对network_header进行了设置。

2)ip_rcv()中详细的检查保证了IP头部到netfilter后是完整的。

3)netfilter可以尽情使用ip头部。

2、获取tcp头部

错误1:

tcph = tcp_hdr(skb);

陷阱:

netfilter的钩子点是属于TCP/IP协议栈的三层流程中,而四层的TCP头部此时还没有正确获取,只是初始化为IP头部的值,无法直接使用。

错误2:

tcph = (char *)iph + (iph->ihl << 2);

陷阱:

数据包可能是非线性的

改进:

tcpoff = skb_network_offset(skb) + (iph->ihl << 2);

tcph = skb_header_pointer(skb, tcpoff, sizeof(_tcph), &_tcph);

if (tcph == NULL)

return;

skb_network_offset(struct skb_buff *skb)

计算三层头部相对于skb->data的偏移

void * skb_header_pointer(struct sk_buff *skb, int offset, int len, void *buffer)

从skb的指定偏移取制定长度的数据,如果要取的数据位于线性区,直接返回其开始指 针,否则,则拷贝到buffer中,并将buffer指针返回。

3、打印IP信息

printk("%pI4 %d -----> %pI4 %d len: %d ID: %d\n",

&iph->saddr,

ntohs(tcph->source),

&iph->daddr,

ntohs(tcph->dest),

ntohs(iph->tot_len),

ntohs(iph->id));

注意要点:

1) IP地址输出

Ipv4:%pI4 %pi4

IPv6:%pI6 %pi6

  1. MAC地址

%pM %pm

3)字节序的转换

ntohs() ntohl() htons() htonl()

__const_ntohl() __const_ntohs() __const_htonl() __const_htons()

区别:_const*()是编译时处理的。

4、获取TCP负载

风险:

payload = (char *)tcph + tcph->doff * 4;

陷阱1:

数据包可能是非线性的,同TCP头部。

陷阱2:

TCP头部数据有可能是被篡改过的,tcph->doff如果很大怎么办?

改进1:

tcplen = skb->len - tcpoff;

if (tcph->doff*4 < sizeof(struct tcphdr) || tcplen < tcph->doff*4)

{

printk("Bad tcp.\n");

return NF_ACCEPT;

}

if (skb_is_nonlinear(skb))

{

printk("Nonlinear skb.\n");

return NF_ACCEPT;

}

payload = (char *)tcph + tcph->doff * 4;

payload_len = tcplen - tcph->doff * 4;

if (payload_len == 0)

return NF_ACCEPT;

接口介绍:

int skb_is_nonlinear(struct sk_buff *skb)

判断skb的数据是否是非线性的

改进2:

char payload_buf[1500];

tcplen = skb->len - tcpoff;

if (tcph->doff*4 < sizeof(struct tcphdr) || tcplen < tcph->doff*4)

{

printk("Bad tcp.\n");

return NF_ACCEPT;

}

payload_len = tcplen - tcph->doff * 4;

if (payload_len == 0)

return NF_ACCEPT;

if (payload_len > sizeof(payload_buf))

return NF_ACCEPT;

payload = skb_header_pointer(skb, tcpoff + tcph->doff*4, payload_len, payload_buf);

if (payload == NULL)

return NF_ACCEPT;

改进3:

tcplen = skb->len - tcpoff;

if (tcph->doff*4 < sizeof(struct tcphdr) || tcplen < tcph->doff*4)

{

printk("Bad tcp.\n");

return NF_ACCEPT;

}

if (skb_ linearize(skb))

{

printk("Can not linearize skb.\n");

return NF_ACCEPT;

}

payload = (char *)tcph + tcph->doff * 4;

payload_len = tcplen - tcph->doff * 4;

if (payload_len == 0)

return NF_ACCEPT;

接口介绍:

int skb_ linearize (struct sk_buff *skb)

将skb线性化

5、解析数据

1)判断数据包内容

风险1:

if (payload[0] != 'G' || payload[1] != 'E' || payload[2] != 'T')

风险2:

if ((payload[0] == 'G' && payload[1] == 'E' && payload[2] == 'T') && payload_len == 3)

陷阱:

如果payload的长度只有1个字节怎么办?

改进:

if (payload_len < 3 || payload[0] != 'G' || payload[1] != 'E' || payload[2] != 'T')

  1. 查找数据包中的某个字符串

风险:

host = strstr(payload, "Host: ");

陷阱:

可能会越界,数据包不一定是以'\0'结束。

改进:

host = strnstr(payload, "Host: ", payload_len);

if (host == NULL)

return ;

一定要使用这一系列的函数:

strnchr

strncpy

strncat

strncmp

strnicmp

strnlen

memcpy

3)移动指向数据包的指针

风险:

host = strnstr(payload, "Host: ", payload_len);

if (host == NULL)

return ;

host = host + sizeof("Host: ") - 1;

/*

* deal with host

*/

陷阱:

查找的字符串有可能是数据包的最后一部分。

改进:

host = strnstr(payload, "Host: ", payload_len);

if (host == NULL)

return ;

host = host + sizeof("Host: ") - 1;

if (host >= (payload + payload_len))

return;

/*

* deal with host

*/

4)数据包操作

错误:

u32 len;

len = payload_len - 512;

if (len <= 0)

return;

memcpy(buf, payload, len);

陷阱:

无符号数的强制类型转换,u32类型永远都是大于等于0的,当payload_len小于512 时,判断就会不生效。

改进:

u32 len;

if (payload_len <= 512)

return;

len = payload_len - 512;

memcpy(buf, payload, len);

或者

int len;

len = payload_len - 512;

if (len <= 0)

return;

memcpy(buf, payload, len);

风险:

int len = payload[1];

memcpy(buf, &payload[2], len);

陷阱:

可能是异常数据包,offset不是你想要的

正确做法:

nt len = payload[1];

if (len >= payload_len - 2)

return;

memcpy(buf, &payload[2], len);

综述:数据包处理要时刻保持警醒,它可能不是你想象的样子!

相关推荐
lixzest1 小时前
Vim 快捷键速查表
linux·编辑器·vim
ICscholar7 小时前
ExaDigiT/RAPS
linux·服务器·ubuntu·系统架构·运维开发
sim20207 小时前
systemctl isolate graphical.target命令不能随便敲
linux·mysql
米高梅狮子8 小时前
4. Linux 进程调度管理
linux·运维·服务器
再创世纪9 小时前
让USB打印机变网络打印机,秀才USB打印服务器
linux·运维·网络
fengyehongWorld10 小时前
Linux ssh端口转发
linux·ssh
知识分享小能手11 小时前
Ubuntu入门学习教程,从入门到精通, Ubuntu 22.04中的Shell编程详细知识点(含案例代码)(17)
linux·学习·ubuntu
yugi98783811 小时前
异构网络下信道环境建模方法及应用
开发语言·网络
Xの哲學12 小时前
深入解析 Linux systemd: 现代初始化系统的设计与实现
linux·服务器·网络·算法·边缘计算
龙月12 小时前
journalctl命令以及参数详解
linux·运维