目录
1.IP报文结构讲解
2.IP报文分片讨论
1 ip报文的解析
版本:IP报文版本号 IPV4:4,IPV6:6
要判:值为4
首部长度:IP header 长度,单位是4字节, 没有选项,则一般为5(5x32bit=20B)
要记住这个长度,用来计算数据区域的起始位置
8位服务类型:一般没有使用,详细参考RFC
不用管
总长度:header+数据 总长度
要使用来计算数据区域的长度
数据区域的长度 = 总长度 - ip报头长度
3.IP报文解析的实现
16位标识:IP 报文的唯一id,分片报文的id 相同,便于进行重组。
3位标志:标明是否分片。是一个3位的控制字段,包含:
保留位:1位
不分段位:1位,取值:0(允许数据报分段)、1(数据报不能分段)
更多段位:1位,取值:0(数据包后面没有包,该包为最后的包)、1(数据包后面有更多的包)
判断思路:只判断第一个分片,其他分片先不管,第一个分片的判断条件就是为0
TTL:生存时间,即路由器的跳数,每经过一个路由器,该TTL 减一,因此路由器需要重新计算IP报文的校验和。
不用管
8位协议:ICMP:1,TCP:6,UDP:17,其他的自行百度
必须判断,而且是区分数据区域的重要标准
首部校验和:IP header校验和,接收端收到报文进行计算如果校验和错误,直接丢弃。
目前先默认校验和是对的,不管
4.日志模块的讨论
源IP地址:无须解释
目的IP地址:无须解释
选项:这个一般也没有使用。详细参考RFC
不需要管
ip报头的结构定义
struct iphdr
{
#if __BYTE_ORDER == __LITTLE_ENDIAN //小端字节序的情况下
unsigned int ihl:4; //位域,该字段只占用4位
unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN //大端字节序的情况下
unsigned int version:4;
unsigned int ihl:4;
#else
# error "Please fix <bits/endian.h>"
#endif
uint8_t tos;
uint16_t tot_len;
uint16_t id;
uint16_t frag_off;
uint8_t ttl;
uint8_t protocol;
uint16_t check;
uint32_t saddr;
uint32_t daddr;
/*The options start here. */
};
5.TCP报头解析
注意,分片只处理第一个分片, frag_off网络字节序的后13位 == 0
按位与
frag_off = 1110000000000000
跟这个数按位与 0001111111111111 0x01ff
6.复习
2 日志模块
1 并发问题
线程1 写 aaaaaaa 到文件
线程2 写 bbbbbbb 到文件
最后可能日志就串在一起 : aabbababb
2 日志写到哪里
1 文件
2 服务器 syslog
做手机游戏,游戏闪退,收集手机端游戏日志
3 日志的格式
统一,拥有的字段: pid tid uid .... 模块 函数名 文件第几行 .....
能够方便之后进行二次处理
开源的日志模块
4 设计上
制作wrapper , 转接口
使用宏
#define DPI_LOG_DEBUG(.....) fprintf(stderr,__VA_ARGS__)
7.协议遍历的实现
3 TCP报文解析思路
struct tcphdr [202/374]
{
uint16_t source;
uint16_t dest;
uint32_t seq;
uint32_t ack_seq;
uint16_t doff:4; //tcp 头部长度
uint16_t res1:4;
uint16_t res2:2;
uint16_t urg:1;
uint16_t ack:1;
uint16_t psh:1;
uint16_t rst:1;
uint16_t syn:1;
uint16_t fin:1;
uint16_t window;
uint16_t check;
uint16_t urg_ptr;
};
8.流程回顾以及调整
4 具体协议报文分析
判断报文属于什么应用协议,对应的报文数量++
if(dpi_pkt_ssh_analyze(pkt))
{
res->ssh_count++;
}
else if(dpi_pkt_ftp_analyze(pkt))
{
res->ftp_count++;
}
........
每次要扩展一个协议,那么就要添加一个if else对,比较繁琐
对于这些类似的代码,或者结构,想办法做成一个循环
将这些协议报文分析的函数放到一个容器中,做遍历,最终遍历某个函数成功识别了报文,对应报文数量++就行
解决的问题:
1 容器
数组,存函数指针
2 函数调用
遍历数组
for(.....)
{
res = arr[i](pkt);
....
}
3 报文数量结果的存储
结果也存到一个数组中,方便之后进行输出
4 扩展性
下次如果还要添加新的协议识别函数应该怎么做,尽量减少工作量
9.SSH协议分析以及解析思路
定义一个枚举,当前项目支持什么协议
typedef enum dpi_protocol_tcp
{
ProtocolBegin=-1,
SSH,
FTP,
ProtocolEnd
}dpi_protocol_tcp;
//定义一个函数指针,专门用来识别协议报文
typedef int (*dpi_protocol_analyze_func_t)(dpi_pkt *pkt);
dpi_protocol_analyze_func_t dpi_analyze_funcs[ProtocolEnd];
10.识别ssh版本协商报文1
5 ssh报文识别思路
流程:
1 软件版本协商交换
客户端告诉服务器:
SSH-2.0-OpenSSH_8.0
服务器告诉客户端:
SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.4
这类型报文容易识别
2 算法协商
大家列出支持的加密算法,从中挑选一个来进行通讯
3 公密钥交换
......
4 正常通讯,ssh操作
密文
11.识别ssh版本协商报文2
目前要识别的是TCP的应用层协议
TCP是面向连接,只要连接没有断开,跑在上面的协议是唯一
只要有一次被抓到识别是什么协议的,这个连接的报文你都认为是这个协议就行
识别条件:
报文如果前面4个字节 是 SSH- 默认是ssh报文
剩余报文的识别思路:
直接标识这个连接的报文都是ssh报文就行
12.剩余报文识别思路
1 如何标识一个连接?
struct dpi_connection
{
uint32_t src_ip;
uint16_t src_port;
uint32_t dst_ip;
uint16_t dst_port;
dpi_protocol protocol; //该连接是什么协议
};
2 如何管理这些链接,下次能够查找得到?
将这些连接放到一个容器,自己造一个容器