内核数据包处理注意事项
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
数据包处理注意事项
前言
我们大部分功能都需要解析数据,进行一系列的包匹配完成,但是目前,我们没有一个很好的框架来简化这个过程,大家处理数据包都是采用原生的linux内核接口,并且没有统一的规范要求如何使用这些接口,所以,存在大量的陷阱,一不留神就造成宕机。
获取IP头部
1)__netif_receive_skb()在进入三层处理前就对network_header进行了设置。
2)ip_rcv()中详细的检查保证了IP头部到netfilter后是完整的。
3)netfilter可以尽情使用ip头部。
获取tcp头部
错误1:
陷阱:
netfilter的钩子点是属于TCP/IP协议栈的三层流程中,而四层的TCP头部此时还没有正确获取,只是初始化为IP头部的值,无法直接使用。
错误2:
陷阱:
数据包可能是非线性的
改进:
计算三层头部相对于skb->data的偏移
从skb的指定偏移取制定长度的数据,如果要取的数据位于线性区,直接返回其开始指针,否则,则拷贝到buffer中,并将buffer指针返回。
打印信息
1) IP 地址输出
Ipv4:%pI4%pi4
IPv6:%pI6
%pi6
2) MAC 地址
%pM %pm
3)字节序的转换
ntohs()ntohl()htons() htonl()
__const_ntohl()__const_ntohs() __const_htonl() __const_htons() 区别:__const_*()是编译时处理的。
获取TCP 负载
风险:
陷阱1:
数据包可能是非线性的,同TCP 头部。
陷阱2:
TCP 头部数据有可能是被篡改过的,tcph->doff 如果很大怎么办?
改进1:
接口介绍:
判断skb的数据是否是非线性的改进2:
改进3:
接口介绍:
将skb线性化
解析数据
1)判断数据包内容
风险1:
风险2:
陷阱:
如果payload的长度只有1个字节怎么办?
改进:
2) 查找数据包中的某个字符串
风险:
陷阱:
可能会越界,数据包不一定是以'\0'结束。
payload_len
payload
改进:
一定要使用这一系列的函数:
strnchr
strncpy
strncat
strncmp
strnicmp
strnlen
memcpy
3)移动指向数据包的指针
风险:
payload_len
payload
陷阱:
查找的字符串有可能是数据包的最后一部分。
payload_len
payload
改进:
4)数据包操作
错误:
陷阱:
无符号数的强制类型转换,u32类型永远都是大于等于0的,当payload_len小于512时,判断就会不生效。
改进:
或者
5)
风险:
陷阱:
可能是异常数据包,offset不是你想要的
正确做法:
综述:数据包处理要时刻保持警醒,它可能不是你想象的样子!
内存分配
风险:
改进:
问题:kmalloc(0, ...)返回值是什么?
建议:相同的内存反复申请释放的情况下,请使用kmem_cache_alloc
建议的同步与互斥方法
1)rcu锁
使用场景:进程上下文用来配置,软中断上下文只读配置的情况
好处:性能高,接口简单
方法:
hook函数读取配置,中断上下文:
基于proc文件等的配置下发,进程上下文:
另一种方法:
注意1:synchronize_rcu()只能用于进程上下文,call_rcu()可以用于中断上下文。
注意2:data_free_rcu的调用是软中断上下文,不能使用vfree。
模块卸载:
2)每CPU变量
使用场景:
在钩子函数中使用的临时缓存区,不用每次申请释放,使用全局变量。
方法:
hook函数:
模块加载:
模块卸载:
注意:
alloc_percpu()上限32k