一文教你如何用C代码解析一段网络数据包?【含代码】
本文的目的是通过随机截取的一段网络数据包,然后根据协议类型来解析出这段内存。
学习本文需要掌握的基础知识:
- 网络协议
- C语言
- Linux操作
- 抓包工具的使用
其中抓包工具的安装和使用见下文:
视频教学链接如下:
一、截取一个网络数据包
通过抓包工具,随机抓取一个tcp数据包

科莱抓包工具解析出的数据包信息如下:

数据包的内存信息:

数据信息可以直接拷贝出来:

二、用到的结构体
下面,一口君就手把手教大家如何解析出这些数据包的信息。
我们可以从Linux内核中找到协议头的定义
- 以太头:
drivers\staging\rtl8188eu\include\if_ether.h
struct ethhdr {
unsigned char h_dest[ETH_ALEN]; /* destination eth addr */
unsigned char h_source[ETH_ALEN]; /* source ether addr */
unsigned short h_proto; /* packet type ID field */
};
- IP头
include\uapi\linux\ip.h
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD) //小端模式
__u8 ihl:4,
version:4;
#elif defined(__BIG_ENDIAN_BITFIELD) //大端模式
__u8 version:4,
ihl:4;
#endif
__u8 tos;
__u16 tot_len;
__u16 id;
__u16 frag_off;
__u8 ttl;
__u8 protocol;
__u16 check;
__u32 saddr;
__u32 daddr;
/*The options start here. */
};
tcp头
include\uapi\linux\tcp.h
struct tcphdr {
__be16 source;
__be16 dest;
__be32 seq;
__be32 ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u16 res1:4,
doff:4,
fin:1,
syn:1,
rst:1,
psh:1,
ack:1,
urg:1,
ece:1,
cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
__u16 doff:4,
res1:4,
cwr:1,
ece:1,
urg:1,
ack:1,
psh:1,
rst:1,
syn:1,
fin:1;
#else
#error "Adjust your <asm/byteorder.h> defines"
#endif
__be16 window;
__sum16 check;
__be16 urg_ptr;
};
因为协议头长度都是按照标准协议来定义的,
所以以太长度是14,
IP头长度是20,
tcp头长度是20,
各个协议头对应的内存空间如下:

三、解析以太头
#define MAC_ARG(p) p[0],p[1],p[2],p[3],p[4],p[5]
struct ethhdr *ethh;
unsigned char *p = pkt;
ethh = (struct ethhdr *)p;
printf("h_dest:%02x:%02x:%02x:%02x:%02x:%02x \n", MAC_ARG(ethh->h_dest));
printf("h_source:%02x:%02x:%02x:%02x:%02x:%02x \n", MAC_ARG(ethh->h_source));
printf("h_proto:%04x\n",ntohs(ethh->h_proto));
注意,数据包中的数据是网络字节序,如果要提取数据一定要注意字节序问题
ethh->h_proto 是short类型,占2个字节,所以存储到本地需要使用函数ntohs
其中:
n:network 网络字节序
h:host 主机字节序
s:short 2个字节
l:long 4个字节
ntohl() :4字节网络字节序数据转换成主机字节序
htons() :2字节主机字节序数据转换成网络字节序
ntohs() :2字节网络字节序数据转换成主机字节序
htonl() :4字节主机字节序数据转换成网络字节序
当执行下面这条语句时,
ethh = (struct ethhdr *)p;
结构体指针变量eth的成员对应关系如下:

最终打印结果如下:

四、解析ip头
解析ip头思路很简单,
就是从pkt头开始偏移过以太头长度(14字节)就可以找到IP头,
解析代码如下:
#define IP_ARG(p) p[0],p[1],p[2],p[3]
/*
解析IP头
*/
if(ntohs(ethh->h_proto) == 0x0800)
{
iph = (struct iphdr *)(p + sizeof(struct ethhdr));
q = (unsigned char *)&(iph->saddr);
printf("src ip:%d.%d.%d.%d\n",IP_ARG(q));
q = (unsigned char *)&(iph->daddr);
printf("dest ip:%d.%d.%d.%d\n",IP_ARG(q));
}

最终解析结果如下:
可以看到我们正确解析出了IP地址,
结果与抓包工具分析出的数据保持了一致。
其中protocol字段表示了ip协议后面的额协议类型,常见的值如下:
| 数值 | 描述 |
|---|---|
| 0 | 保留字段,用于IPv6(跳跃点到跳跃点选项) |
| 1 | Internet控制消息 (ICMP) |
| 2 | Internet组管理 (IGMP) |
| 3 | 网关到网关 (GGP) |
| 4 | 1P中的IP(封装) |
| 6 | 传输控制 (TCP) |
| 7 | CBT |
| 8 | 外部网关协议 (EGP) |
| 9 | 任何私有内部网关(Cisco在它的IGRP实现中使用) (IGP) |
| 10 | BBNRCC监视 |
| 11 | 网络语音协议 |
| 12 | PUP |
| 13 | ARGUS |
| 14 | EMCON |
| 15 | 网络诊断工具 |
| 16 | 混乱(Chaos) |
| 17 | 用户数据报文 (UDP) |
| 41 | 1Pv6 |
| 58 | 1Pv6的ICMP |
| 59 | 1Pv6的无下一个报头 |
| 60 | IPv6的信宿选项 |
| 89 | OSPF IGP |
| 92 | 多播传输协议 |
| 94 | IP内部的IP封装协议 |
| 95 | 可移动网络互连控制协议 |
| 96 | 旗语通讯安全协议 |
| 97 | IP中的以太封装 |
| 98 | 封装报头 |
| 100 | GMTP |
| 101 | Ipsilon流量管理协议 |
| 133~254 | 未分配 |
| 255 | 保留 |
五、解析tcp头
查找tcp头思路很,
就是从pkt头开始偏移过以太头长度(14字节)、和IP头长度(20字节)就可以找到tcp头,
switch(iph->protocol)
{
case 0x1:
//icmp
break;
case 0x6:
//tcp
tcph = (struct tcphdr *)(p + sizeof(struct ethhdr) + sizeof(struct iphdr));
printf("source:%d dest:%d \n",ntohs(tcph->source),ntohs(tcph->dest);
break;
case 0x11:
//udp
break;
}
结构体与内存对应关系

打印结果如下:

六、学会用不同格式打印这块内存
在实际项目中,可能我们解析的并不是标准的TCP/IP协议数据包,
可能是我们自己的定义的协议数据包,
只要掌握了上述方法,
所有的协议分析都能够手到擒来!
有时候我们还需要打印对方发送过来的数据帧内容,
往往我们会以16进制形式将所有数据打印出来,
这样是最有利于我们分析数据内容的。
1. 按字节打印
代码如下:
for(i=0;i<400;i++)
{
printf("%02x ",pkt[i]);
if(i%20 == 19)
{
printf("\n");
}
}

2. 按short类型分析一段内存
我们接收数据时,虽然使用一个unsigned char型数组,
但是有时候对方发送过来的数据可能是2个字节的数组,
那我们只需要用short类型的指针,指向内存的头,
然后就可以通过该指针访问到对方发送的数据,
这个时候一定要注意字节序问题,
不同场景可能不一样,所以一定要具体问题具体分析,
本例因为是网络字节序数据转换成主机字节序,
所以需要转换字节序。
//转变short型字节序
void indian_reverse(unsigned short arr[],int num)
{
int i;
unsigned short temp;
for(i=0;i<num;i++)
{
temp = 0;
temp = (arr[i]&0xff00)>>8;
temp |= (arr[i]&0xff)<<8;
arr[i] = temp;
}
}
main()
{
unsigned short spkt[200];
………………
memcpy(spkt,pkt,sizeof(pkt));
indian_reverse(spkt,ARRAY_SIZE(spkt));
for(i=0;i<200;i++)
{
printf("%04x ",spkt[i]);
if(i%10 == 9)
{
printf("\n");
}
}
………………
}
结果如下:

完整代码请关注公众号:一口Linux,回复:数据包解析
一文教你如何用C代码解析一段网络数据包?【含代码】的更多相关文章
- 一个C++版的网络数据包解析策略
C++版的网络数据包解析策略(升级版) 一.数据包格式形如下图 二.代码 int ReceiveFromRemoteEndPoint() { int nPackageDataLength = ; ch ...
- Python 1行代码实现文本分类(实战笔记),含代码详细说明及运行结果
Python 1行代码实现文本分类(实战笔记),含代码详细说明及运行结果 一.详细说明及代码 tc.py =============================================== ...
- C++版的网络数据包解析策略(升级版)
初版:http://www.cnblogs.com/wjshan0808/p/6580638.html 说明:在实现了对应的接口后该策略可以适合绝大多数的网络数据包结构 首先,是三个接口 IProdu ...
- lwip:网络数据包读取和解析过程
1. 程序的某处(poll or interrupt)在有数据可读时调用ethernetif_input,该函数依次调用以下函数: 1.1 low_level_input(),将网络数据读入内存: 1 ...
- MINA 网络黏包处理代码
本文完整代码,可以浏览: https://github.com/hjj2017/xgame-code_server/blob/master/game_server/src/com/game/gameS ...
- lua中是 ffi 解析 【是如何处理数据包的/pkt是如何传进去的】 fsfsfs
lua中的ffi是如何解析的呢? 拿bcc中对proto的解析说起: metatype是有大学问的: ffi.metatype(ffi.typeof('struct ip_t'), { __index ...
- IP报文解析及基于IP 数据包的洪水攻击
版本(4bit) 报头长度(4bit) 优先级和服务类型(8bit) 总长度(16bit) 标识(16bit) 标志(3bit) 分段偏移(13bit) 存活期(8bit) 协议(8bit) 报头校验 ...
- Beam Search快速理解及代码解析
目录 Beam Search快速理解及代码解析(上) Beam Search 贪心搜索 Beam Search Beam Search代码解析 准备初始输入 序列扩展 准备输出 总结 Beam Sea ...
- IM通信协议逆向分析、Wireshark自定义数据包格式解析插件编程学习
相关学习资料 http://hi.baidu.com/hucyuansheng/item/bf2bfddefd1ee70ad68ed04d http://en.wikipedia.org/wiki/I ...
- 承载于以太网帧之上的数据包的解析——ARP、IPv4、IPv6
承接上一博文而来,继续解析网络数据包,对于承载在以太网上的三种协议进行了解析,主要是分为依据RFC定义的标准先解析头部数据,然后得到有效载荷,即为协议包括的实体数据,更上层进行进一步处理. 一.ARP ...
随机推荐
- 在Xcode编写我们的quick help
程序环境 Xcode 13.2.1 "写代码要经常写注释."虽然很多人不以为然,但是在使用Xcode的时候,我们在写注释的时候稍微注意一下格式的话,Xcode会给我们带来额外的正反 ...
- python重拾第十二天-MYSQL数据库
本节内容 数据库介绍 mysql 数据库安装使用 mysql管理 mysql 数据类型 常用mysql命令 创建数据库 外键 增删改查表 权限 事务 索引 python 操作mysql 1. 数据库介 ...
- 在SMT32程序HEX文件中加入固件版本信息
将固件版本信息定义成一个常量存放在程序中,需要的时候可以通过串口等接口查询代码版本信息,另一种,是利用编译器的语法,将固定的信息保存到某地. #define VERINFO_ADDR_BASE (0x ...
- IPv6地址的文本表示规范
背景 随着IPv6越来越普及,经常要跟IPv6地址打交道,迫切需要一个统一的IPv6地址文本表示规范. RFC4291简单的说明了如何将IPv6地址表示成文本形式,但有很多有歧义和不周全的地方. RF ...
- Hadoop集群管理之fsimage和edits工作机制
客户端对hdfs进行写文件时会首先被记录在edits文件中. edits修改时元数据也会更新. 每次hdfs更新时edits先更新后客户端才会看到最新信息. fsimage:是namenode中关于元 ...
- 深度学习领域的名词解释:SOTA、端到端模型、泛化、RLHF、涌现 ..
SOTA (State-of-the-Art) 在深度学习领域,SOTA指的是"当前最高技术水平"或"最佳实践".它用来形容在特定任务或领域中性能最优的模型或方 ...
- vulnhub - w1r3s.v1.0.1
vulnhub - w1r3s.v1.0.1 高质量视频教程 - b站红队笔记 靶机下载 本地环境 本机ip:192.168.157.131 w1r3s虚拟机设置NAT模式 信息收集 扫描网段得到攻击 ...
- SpringBoot的知识点总结和常用注解
SpringBoot 知识点总结 基础入门 基本介绍.基本特性.核心模块.版本选择.环境要求.安装集成.技快速开发接口.Maven Wrapper.Spring Boot CLl 配置管理 配置类.配 ...
- 1. 介绍一下 CSS 的盒子模型?
1. 盒模型: 内容(content).填充(padding).边界(margin). 边框(border) 2. 类型: IE 盒子模型.标准 W3C 盒子模型: 3. 两种盒模型的主要区别是:标准 ...
- 【JavaScript】js中的浅拷贝与深拷贝与手写实现
前言 什么是深拷贝与浅拷贝?深拷贝与浅拷贝是js中处理对象或数据复制操作的两种方式.在聊深浅拷贝之前咱得了解一下js中的两种数据类型: 基本数据类型(6种) String.Number.Object ...