【编程笔记】libpcap应用之打印TCP会话

一、libpcap之TCP会话

在实际采集数据包的场景中,通常是需要提取应用层相关的信息,如提取域名的请求记录、HTTP协议的请求记录或者是在数据流中还原传输的文件等。而提取这些信息,有时就可能不只是从单个数据包中获取。那么,如果在网络数据流中要组合TCP会话数据应该如何来做呢?

二、libpcap之TCP会话计算方式

  • 摘自:通义千问, TCP网络流的计算方式即是将源IP、源端口、目的IP、目的端口组合作为唯一标识,也就是同一会话:正向(sip,sport,dip,dport)与反向(dip,dport,sip,sport)对比相同即可。
在数据包中,为了将数据包归到一个特定的网络流(也称为会话、连接),通常确实会使用源IP地址(Source IP, sip)、源端口号(Source Port, sport)、目的IP地址(Destination IP, dip)和目的端口号(Destination Port, dport)这四个参数来共同标识一个唯一的网络流。这些参数组合在一起被称为“四元组”,用于区分不同网络会话中的数据包。

**四元组的作用**:

1. **源IP地址(sip)**:标识数据包的发送者(源主机)的网络地址。
2. **源端口号(sport)**:在多路复用的环境中,同一主机上的多个应用程序可能同时进行网络通信。源端口号用于标识发送数据包的具体应用程序。

3. **目的IP地址(dip)**:标识数据包的接收者(目的主机)的网络地址。
4. **目的端口号(dport)**:与源端口号类似,目的端口号用于标识接收数据包的具体应用程序。

**四元组作为流标识**:

结合这四个参数,就可以唯一确定一个网络流:

- 对于TCP或UDP这样的面向连接的协议,四元组能够唯一标识一个TCP连接或UDP会话。在同一个连接或会话中传输的所有数据包都具有相同的四元组,可以归为同一网络流进行处理或分析。
- 对于无连接的协议(如某些ICMP消息),虽然不涉及端口号,但仍然可以通过源IP和目的IP来识别相关的数据包流。

**实际应用**:

在各种网络设备、软件和服务中,如路由器、防火墙、网络监控工具、负载均衡器等,都会利用四元组来识别、跟踪、管理和控制网络流:

- 网络设备基于四元组进行路由决策。
- 防火墙和安全设备根据四元组规则过滤或允许特定的网络流量。
- 网络监控工具使用四元组统计数据包、测量带宽、识别异常流量等。
- 负载均衡器依据四元组分配请求到后端服务器,确保来自同一客户端的请求始终被定向到同一台服务器,保持会话状态的一致性。

总之,源IP地址、源端口号、目的IP地址、目的端口号这四个参数共同构成了网络流的唯一标识,是将数据包归入相应流的关键依据。在实际应用中,通过四元组可以有效地管理和分析网络流量。

三、libpcap之TCP会话实现思路

  • 摘自:通义千问, 打印TCP会话(四元组)编程思路如下:
  1. 引入必要的头文件;
  2. 使用libpcap库打开网络设备并设置过滤规则;
  3. 循环处理捕获到的数据包;
  4. 提取数据包中的四元组(源IP、源端口、目的IP、目的端口)信息。
  5. 根据四元组信息判断是否为新的会话,并输出新增会话信息。

四、libpcap之TCP会话输出

1. 指定采集的网络接口以及设置过滤规则

利用pcap_open_live()函数打开网络接口(通过命令行传入,如eth0);其次,再通过pcap_compile()、pcap_setfilter()函数应用过滤规则,即设置只捕获TCP协议的流量数据。

// 打开指定网络接口
handle = pcap_open_live(argv[1], BUFSIZ, 1, 1000, NULL);
if (handle == NULL) {
	fprintf(stderr, "Error opening interface.\n");
	return -1;
}

// 设置过滤规则(例如,仅捕获TCP流量)
if (pcap_compile(handle, &bpf, "tcp", 1, 0) == -1) {
	fprintf(stderr, "Couldn't parse filter: %s\n", pcap_geterr(handle));
	return(2);
}

if (pcap_setfilter(handle, &bpf) == -1) {
	fprintf(stderr, "Couldn't install filter: %s\n", pcap_geterr(handle));
	return(2);
}

2.数据包的报文结构

计算四元组,需要从数据报文中提取相应的IP地址和端口信息。那么,需要认识数据包的报文格式,才能对报文解析:
–数据链路层,记录源、目的MAC地址以及下一层TYPE等信息,一般固定为14个字节长度,可以使用 struct ether_header来解析 ;
–IP层,记录了源、目的IP以及TTL等信息,一般固定为20个字节长度,可以使用struct ip来解析;
–TCP层,记录了源、目的端口以及TCP标志等信息,一般固定为20个字节长度+40字节可选数据,可以使用 struct tcphdr来解析。
在这里插入图片描述
下面是结构体的定义说明:

# struct ether_header是一个结构体,用于存储以太网数据包的头部信息。以太网数据包是网络接口层使用的一种常见数据格式。ether_header结构体通常定义在netinet/ether.h头文件中。
struct ether_header {
    u_int8_t ether_dhost[6]; /* destination eth addr */
    u_int8_t ether_shost[6]; /* source ether addr */
    u_int16_t ether_type;    /* packet type ID field */
};

# struct ip是C语言中用来表示Internet Protocol (IP)报头的数据结构,它封装了IP层协议传输的数据包头信息。iphdr 结构体通常定义在netinet/ip.h头文件中。
# 注意,实际使用时,`struct ip`的具体定义可能受到平台或库的影响,尤其是在处理不同字节序(小端/大端)的系统时,某些字段可能使用位域(bit fields)进行更精细的控制。此外,对于IPv6数据包,应当使用对应的`struct ip6_hdr`(或其他名称,视具体实现而定)来表示IPv6报头。
struct ip {
    unsigned char   ip_hl:4;       /* IP头部长度(单位:32位字,即4字节) */
    unsigned char   ip_v:4;        /* 版本号(IPv4为4) */
    unsigned char   ip_tos;        /* 服务类型(ToS,Type of Service) */
    unsigned short  ip_len;        /* 整个IP数据包的总长度(包括报头和数据) */
    unsigned short  ip_id;         /* 标识符(Identification) */
    unsigned short  ip_off;        /* 分片偏移(Fragment Offset) */
    unsigned char   ip_ttl;        /* 生存时间(Time to Live) */
    unsigned char   ip_p;          /* 协议类型(Protocol,如TCP、UDP等) */
    unsigned short  ip_sum;        /* IP头部校验和(Checksum) */
    struct in_addr  ip_src;        /* 源IP地址 */
    struct in_addr  ip_dst;        /* 目的IP地址 */
};
# struct tcphdr是一个常见的结构体,它用于表示TCP(传输控制协议)首部。TCP首部定义了TCP数据包所包含的一些重要信息,例如源和目的端口号、序列号、确认号以及一些控制标志等。
# <netinet/tcp.h>:在Linux系统中,这个头文件定义了TCP和UDP协议的数据结构;
# <winsock2.h>:在Windows系统中,这个头文件定义了网络编程相关的数据结构和函数。
struct tcphdr {
    unsigned short th_sport;   /* source port */
    unsigned short th_dport;   /* destination port */
    unsigned int th_seq;       /* sequence number */
    unsigned int th_ack;       /* acknowledgement number */
    unsigned char th_offx2;    /* data offset, rsvd */
    unsigned char th_flags;    /* TCP flags */
    unsigned short th_win;     /* window */
    unsigned short th_sum;     /* checksum */
    unsigned short th_urp;     /* urgent pointer */
};

3.循环采集数据,并提取四元组数据存储至结构体变量中

通过while循环不断调用pcap_next()函数获取循环数据包,其后利用各层的结构体(struct ether_header*) 、(struct ip*)、(struct tcphdr*)对数据报文进行解析,提取出计算四元组所需的信息:IP地址、端口。
备注:提取出的IP地址与端口信息,需要利用inet_ntoa、ntohs函数进行转换处理,再将处理后的数据存入临时的结构体中,之后再调用检查函数判断是否有新会话及输出。

while (1) {
	packet = pcap_next(handle, &header);
	
	// 解析以太网帧头
	eth_header = (struct ether_header*)packet;
	if (ntohs(eth_header->ether_type) != ETHERTYPE_IP) {
	    continue;  // 非IP包,跳过
	}
	
	// 解析IP包头
	ip_header = (struct ip*)(packet + sizeof(struct ether_header));
	if (ip_header->ip_p != IPPROTO_TCP) {
	    continue;  // 非TCP包,跳过
	}
	
	// 解析TCP包头
	tcp_header = (struct tcphdr*)((u_char*)ip_header + (ip_header->ip_hl << 2));
	
	// 提取四元组信息
	strcpy(session.src_ip_str, inet_ntoa(ip_header->ip_src));
	strcpy(session.dst_ip_str, inet_ntoa(ip_header->ip_dst));
	session.src_port = ntohs(tcp_header->th_sport);
	session.dst_port = ntohs(tcp_header->th_dport);
	
	// 检查并输出新会话
	check_and_output_new_session(&session);
}

4.检查数据包并输出新会话

定义会话结构体和设置特定大小的哈希表,用以记录捕获数据报文的IP地址与端口信息以及出现的TCP会话。定义的会话结构体以及用以存储已知会话的哈希表(其中,INET_ADDRSTRLEN的长度为16):

// 定义会话结构体
typedef struct {
	char src_ip_str[INET_ADDRSTRLEN];
	uint16_t src_port;
	char dst_ip_str[INET_ADDRSTRLEN];
	uint16_t dst_port;
} Session;

// 用于存储已知会话的哈希表
#define MAX_SESSIONS 1024
Session sessions[MAX_SESSIONS];
int session_count = 0;

下面为数据包的检查函数,通过循环遍历已存储的哈希表依次对比,即可判断数据包是否已存在,如果不存在即表示是新的TCP会话,在此之后记录该新会话并打印:

void check_and_output_new_session(Session* new_session) {
	int i;
	for (i = 0; i < session_count; ++i) {
	    if ((strcmp(new_session->src_ip_str, sessions[i].src_ip_str) == 0 &&
	         new_session->src_port == sessions[i].src_port &&
	         strcmp(new_session->dst_ip_str, sessions[i].dst_ip_str) == 0 &&
	         new_session->dst_port == sessions[i].dst_port) ||
	        (strcmp(new_session->src_ip_str, sessions[i].dst_ip_str) == 0 &&
	         new_session->src_port == sessions[i].dst_port &&
	         strcmp(new_session->dst_ip_str, sessions[i].src_ip_str) == 0 &&
	         new_session->dst_port == sessions[i].src_port)) {
	        return;  // 已知会话,无需输出
	    }
	}
	
	// 新会话,输出会话信息并添加到已知会话列表
	printf("New session detected:\n");
	printf("Src IP: %s, Src Port: %d\n", new_session->src_ip_str, new_session->src_port);
	printf("Dst IP: %s, Dst Port: %d\n\n", new_session->dst_ip_str, new_session->dst_port);
	
	sessions[session_count++] = *new_session;
}

TCP会话输出效果如下所示:
在这里插入图片描述
至此,就实现了对TCP会话的打印,但是需要注意定义的哈希表是有大小限制(其大小表示最大能存储的TCP会话数量),如果需要长时间捕获数据流,需要对此进一步处理。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值