初步认知tcp/ip协议栈
什么是协议栈
TCP/IP(Transmission Control Protocol/Internet Protocol)定义了一组规范和协议,用于在计算机网络中实现数据通信。
协议栈,或称TCP/IP 协议栈,是该网络簇下网络协议实现的集合,也是现代互联网的基础。
TCP/IP 协议栈由多个层级组成,每个层级负责处理特定的网络功能。它与 OSI 模型有所不同,但在基本功能和逻辑上非常相似。TCP/IP 协议栈的四个主要层级如下:
-
网络接口层(Network Interface Layer):也称为物理层和数据链路层,负责将数据帧从物理层传输到网络层,并提供与物理介质的直接交互。
-
网络层(Internet Layer):通过 IP 协议处理数据包的路由选择和转发,将数据包从发送方传输到接收方。其中最常见的协议是 IPv4(Internet Protocol version 4)和 IPv6(Internet Protocol version 6)。
-
传输层(Transport Layer):提供端到端的可靠数据传输服务。最重要的协议是 TCP(Transmission Control Protocol),它通过序列号、确认和重传机制来确保可靠的数据传输。此外,还有 UDP(User Datagram Protocol),它是一种无连接的传输协议,不提供可靠性,但具有更低的延迟和更简单的机制。
-
应用层(Application Layer):实现了各种应用程序的网络服务。应用层协议包括 HTTP(HyperText Transfer Protocol),用于 Web 浏览器和服务器之间的通信;FTP(File Transfer Protocol),用于文件传输;SMTP(Simple Mail Transfer Protocol),用于电子邮件的传输;DNS(Domain Name System),用于域名解析等。
TCP/IP 协议栈是互联网通信的基础,它提供了面向连接的可靠传输、无连接传输和各种应用程序之间的数据交换能力。通过遵循 TCP/IP 协议栈的规范,计算机可以在全球范围内进行互联网通信,并实现数据的传输、路由和应用程序的交互。
协议栈有那些作用
在现代开发中,TCP/IP 协议栈有以下几个重要作用:
-
网络通信:TCP/IP 协议栈提供了网络通信的基础,使得不同设备和应用程序能够通过互联网进行数据交换和通信。
-
可靠传输:通过 TCP 协议,TCP/IP 协议栈实现了可靠的端到端数据传输,确保数据的完整性和顺序性。
-
无连接传输:UDP 协议提供了无连接的传输机制,适用于对延迟敏感的应用,如实时音视频流媒体等。
-
路由选择:IP 协议负责将数据包从源主机传输到目标主机,通过路由选择算法来确定最佳路径,实现网络间的数据包转发。
-
应用支持:TCP/IP 协议栈中的应用层协议提供了各种网络服务,如 Web 浏览器与服务器之间的 HTTP 通信、电子邮件的传输等。
协议栈也在当今开发领域中扮演了重要角色,一些出名的 TCP/IP 相关项目和技术包括:
-
Linux 内核:Linux 内核是一个开源操作系统内核,广泛使用了 TCP/IP 协议栈,为网络通信提供了强大的支持。
-
Apache HTTP Server:Apache 是最流行的开源 Web 服务器软件之一,它使用 TCP/IP 协议栈来提供 HTTP 服务。
-
Nginx:Nginx 是另一个流行的开源 Web 服务器软件,也使用 TCP/IP 协议栈来支持 HTTP 通信。
-
Wireshark:Wireshark 是一个流行的网络协议分析工具,可用于捕获和分析 TCP/IP 协议栈中的数据流,用于网络故障排除和性能优化。
-
OpenVPN:OpenVPN 是一种开源虚拟专用网络(VPN)技术,使用 TCP/IP 协议栈建立安全的远程访问连接。
-
vpp: 一个高性能、可扩展和可定制的网络协议栈产品,适用于构建高性能网络应用或进行网络功能虚拟化等领域的开发和部署。
这些项目和技术在现代开发中发挥着重要的作用,帮助构建稳定、可靠和安全的网络通信环境。
协议栈的简单实现-提取五元组
协议栈并非简单的玩具
一个协议栈产品通常不会独立出现,一般是结合系统进行定制;
一个基础的协议栈将输入的原始数据进行分包解析,特征过滤,深度协议探测(DPI:deep protocol identification),元数据提取,格式转换等操作,将协议的元数据,通联信息,原始负载内容整理发送给后端使用;
在高性能场景下,通常结合DPDK技术将网卡数据DMA到应用层协议栈进行处理,这样就可以绕过NIC和内核协议栈带来的高延迟,从而更快地处理输入数据。
可见网络协议栈并非一个简单的玩具,而是需要大量网络相关研发人员及专家共同维护的大型工程,我在这里提供一些简单的提取pcap文件中五元组信息的工具,目的是更好的让大家初步理解协议栈,日后如果有使用协议栈的需求可以做出更好的选择;
基本原理
可以结合wireshark进行理解,通常,单个pcap文件由一个pcap文件头和N个链路帧构成,我们只需要按照pcap文件格式进行逐帧解析即可。解析数据帧的过程中,可以提取帧中有用的信息,此次主要是提取五元组信息。
核心实现
文件格式定义
#pragma pack(push , 1)
typedef struct _pcap_file_head{
uint32_t magic; ///< pcap文件的幻数
uint16_t version_major; ///< 当前文件的主要版本号,一般为0x0200
uint16_t version_minor; ///< 当前文件的次要版本号,一般为0x0400
uint32_t thiszone; ///< 当地的标准时间,如果用的是GMT则全零,一般全零
uint32_t sigfigs; ///< 时间戳的精度,一般为全零
uint32_t snaplen; ///< 最大的存储长度,设置所抓获的数据包的最大长度,如果所有数据包都要抓获,将值设置为65535
uint32_t linktype; ///< 链路类型。解析数据包首先要判断它的LinkType,所以这个值很重要。一般的值为1,即以太网
}pcap_file_t;
typedef struct _frame_head{
uint32_t Timestamp_sec; ///< 时间戳高位,精度 秒
uint32_t Timestamp_usec; ///< 时间戳低位,精度 微秒
uint32_t Caplen; ///< 到下一帧的偏移
uint32_t Len; ///< 实际上下一帧的偏移,绝大多数情况下等于caplen,可能会小于caplen
}frame_head_t;
typedef struct _comm_eth_head{
uint8_t dst_mac[6]; ///< 目标mac地址
uint8_t src_mac[6]; ///< 源mac地址
uint16_t type; ///< 请求类型 0x0800 ip数据 0x0806 arp 请求 0x8035 rarp 请求
}comm_eth_head_t;
typedef struct _comm_ip_head{
uint8_t version:4, ///< 版本
headlen:4; ///< 头部实际长度
uint8_t TOS; ///< type of service
uint16_t iplen; ///< ip数据报总长度
uint16_t flag; ///< 16位标识
uint16_t falg:3, ///< 3位标志
offset:13;///< 13位片偏移
uint8_t ttl; ///< time to live 生存时间
uint8_t protocol;
uint16_t checksum;
uint32_t src_ip;
uint32_t dst_ip;
/*变长部分暂不关心*/
}comm_ip_head_t;
typedef struct _comm_tcp_head{
uint16_t sport; ///< 源端口
uint16_t dport; ///< 目的端口
uint32_t seq; ///< 序号字段
uint32_t ack; ///< 确认序号应当是上次已成功收到数据字节序号加 1
uint16_t head_len:4, ///< 首部长度
reserved:6, ///< 保留位
UGR:1, ///< 紧急指针
ACK:1, ///< 确认序号有效
PSH:1, ///< 接收方应该尽快将这个报文段交给应用层
RST:1, ///< 重建连接
SYN:1, ///< 同步序号用来发起一个连接
FIN:1; ///< 发端完成发送任务
uint16_t win_size; ///< 窗口大小,T C P的流量控制由连接的每一端通过声明的窗口大小来提供
uint16_t checksum; ///< 检验和覆盖了整个的 T C P报文段:T C P首部和T C P数据
uint16_t urgent_ptr; ///< T C P的紧急方式是发送端向另一端发送紧急数据的一种方式。
/*其他选项*/
}comm_tcp_head_t;
typedef struct _comm_udp_head{
uint16_t sport;
uint16_t dport;
uint16_t headlen; ///< UDP首部和UDP数据的字节长度
uint16_t check_sum;///< udp校验和
}comm_udp_head_t;
#pragma (pop)
解析过程
static int pcap_analyse()
{
pcap_file_t tmp_pcap_file_head;
frame_head_t tmp_frame_head;
int ret; ///< 接收fread的返回值
fread(&tmp_pcap_file_head , sizeof(tmp_pcap_file_head) , 1 ,g_pcap_fp);
if(tmp_pcap_file_head.magic != 0xA1B2C3D4) ///< 校验幻数
{
fprintf(stderr , "magic num check failed\n");
return -1;
}
if(tmp_pcap_file_head.linktype != 1) ///< 不是以太类型的我们也暂时不解析
{
fprintf(stderr , "just support common eth data");
return -1;
}
while(1)
{
ret = fread(&tmp_frame_head , 1 , sizeof(tmp_frame_head) , g_pcap_fp );
if(ret < sizeof(tmp_frame_head))
{
printf("analyse pcap over\n");
return 0;
}
g_pkt_num ++; ///< 增加计数
fread(g_frame_data , tmp_frame_head.Caplen , 1 ,g_pcap_fp ); ///< 将pcap文件的数据读入全局静态内存
pcap_payload_analyse(g_frame_data);
bzero(g_frame_data , tmp_frame_head.Caplen); ///< 清理内存
}
return 0;
}
static int pcap_payload_analyse(uint8_t *frame_payload)
{
comm_eth_head_t *eth_head_ptr = NULL ;
comm_ip_head_t *ip_head_ptr = NULL ;
comm_tcp_head_t *tcp_head_ptr = NULL;
comm_udp_head_t *udp_head_ptr = NULL;
eth_head_ptr = (comm_eth_head_t *)frame_payload;
ip_head_ptr = (comm_ip_head_t*)(frame_payload + sizeof(comm_eth_head_t));
g_ip_datasize += ntohs(ip_head_ptr->iplen); ///< 更新ip数据长度
if(ip_head_ptr->protocol == 6) ///<tcp协议
tcp_head_ptr = (comm_tcp_head_t*)(frame_payload + sizeof(comm_eth_head_t) + sizeof(comm_ip_head_t));
else if(ip_head_ptr->protocol == 17) ///< udp协议
udp_head_ptr = (comm_udp_head_t *)(frame_payload + sizeof(comm_eth_head_t) + sizeof(comm_ip_head_t));
else
{
printf("we just support tcp/udp protocol \n'");
return -1 ;
}
uint32_t tmp_sip = ip_head_ptr->src_ip;
uint8_t *tmp_src_Ptr = (uint8_t *)&tmp_sip;
char sip_buf[16] = {};
inet_ntop (AF_INET, tmp_src_Ptr, sip_buf, sizeof (sip_buf));
uint32_t tmp_dip = ip_head_ptr->dst_ip;
uint8_t *tmp_dst_Ptr =(uint8_t *) &tmp_dip;
char dip_buf[16] = {};
inet_ntop (AF_INET, tmp_dst_Ptr, dip_buf, sizeof (dip_buf));
if(g_print_flag && tcp_head_ptr)
{
printf("sport:%d\t dport:%d\t sip:%s\t dip:%s\t protocol %d \n ",\
htons(tcp_head_ptr->sport) ,htons(tcp_head_ptr->dport),\
sip_buf , dip_buf,ip_head_ptr->protocol );
}
else if(g_print_flag && udp_head_ptr)
{
printf("sport:%d\t dport:%d\t sip:%s\t dip:%s\t protocol %d \n ",\
htons(udp_head_ptr->sport) ,htons(udp_head_ptr->dport),\
sip_buf , dip_buf,ip_head_ptr->protocol );
}
return 0;
}