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

embedded/2024/12/20 13:18:17/

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

2) 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')

2) 查找数据包中的某个字符串

风险:

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);

5)

风险:

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);

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


http://www.ppmy.cn/embedded/147283.html

相关文章

【mybatis】缓存

目录 1. mybatis的运行 1.1 引言 1.2 具体运行&#xff1a; 1.3 sqlSession 介绍local catch 2. 缓存 2.1 概念 2.2 使用缓存的原因 2.3 什么样的数据能使用缓存 3. Mybatis缓存 3.1 一级缓存 3.1.1 测试一级缓存 3.1.2 缓存失效的四种情况 $1 sqlSession不同 $…

[Effective C++]条款35 virtual函数替换方案

本文初发于 “天目中云的小站”&#xff0c;同步转载于此。 条款35 : 考虑virtual函数以外的其他选择 我们都知道使用virtual函数是有代价的, 它会带来额外的开销, 譬如占用内存, 降低效率, 不好控制安全性等问题, 因此如果我们想构建一个逻辑缜密且标准的项目, 可以考虑一些vi…

vivado 高亮相同变量名_显示选定词的匹配项

一、引言 之前使用vivado2018.8的时候双击变量后&#xff0c;相同变量名的变量都会被高亮。但是下载vivado2022.2版本之后&#xff0c;并没有高亮相同变量。如图所示。 二、修改设置 打开设置&#xff0c;选择“Text Editor”&#xff0c;并勾选上"Display matches for th…

uniapp-微信小程序调用摄像头

1.uniapp中的index.vue代码 <template><view class"content"><view class"container"><!-- 摄像头组件 --><camera id"camera" device-position"front" flash"off" binderror"onCameraErr…

Golang囊地鼠gopher

开发知识点-golang 介绍红队专题-Golang工具Fscan简介主要功能ubuntu 安装windows 安装常用命令:项目框架源文件common目录Plugins目录Webscan目录入口点插件扫描类型爆破插件common.ScantypeWebtitle函数webpoc扫描POC 执行CEL-GO 实践CEL指纹识别免杀源码特征参考链接红队专…

【图像分类实用脚本】数据可视化以及高数量类别截断

图像分类时&#xff0c;如果某个类别或者某些类别的数量远大于其他类别的话&#xff0c;模型在计算的时候&#xff0c;更倾向于拟合数量更多的类别&#xff1b;因此&#xff0c;观察类别数量以及对数据量多的类别进行截断是很有必要的。 1.准备数据 数据的格式为图像分类数据集…

进程管理的关键:Linux进程状态与常用命令解析

个人主页&#xff1a;chian-ocean 文章专栏&#xff1a;Linux 前言&#xff1a; 在现代操作系统中&#xff0c;进程是资源分配和任务调度的基本单位。作为一个多任务操作系统&#xff0c;Linux 必须在多个进程之间进行有效的调度和管理&#xff0c;这就需要对每个进程进行状态…

前端打印(html)

目录 1.window.print() 2.使用插件print.js 1.window.print() <template> <div id"contenteBox">内容</div> <button click"printContent">打印</button> </template> <script> export default{ data(){ retu…