协议分析(一)
以太网协议
工作原理
以太网协议是一种局域网通信协议,它通过物理层和数据链路层的协同工作,使用媒体访问控制地址和载波监听/冲突检测协议来实现计算机之间的稳定数据传输。在数据传输过程中,以太网会将数据封装成数据帧,并根据目标MAC地址来识别需要接收数据的计算机。通过这种方式,以太网协议能够保证数据的准确性和完整性,并实现计算机之间的通信与数据传输。主要涉及到物理层和数据链路层:
物理层:以太网使用双绞线或同轴电缆等介质进行数据传输。发送端将数据转换为比特流,并通过物理层将比特流转换为电信号并发送到传输介质中。接收端则将电信号重新转换成比特流。以此来实现物理层数据传输。
数据链路层:以太网使用MAC(媒体访问控制)地址识别不同计算机。当计算机发送数据时,会将目标MAC地址、源MAC地址、以及数据传输类型等信息封装成数据包,并通过物理层发送到介质中。在接收端,数据包被逐层解析,根据MAC地址来识别数据包是否为自己所需的数据。以此来实现数据链路层的数据传输。
数据结构
帧前导码:在每一帧数据的开头,都有7个字节的前导码,用来供接收方同步数据传输时钟。
目的MAC地址:6个字节的MAC地址,指示数据包要发送到的目标设备的物理地址。
源MAC地址:6个字节的MAC地址,指示数据包发送者的物理地址。
类型/长度 :2个字节。在IEEE 802.3中可以表示两种类型的值。当值小于等于0x05DC时,表示数据包的长度,当值大于0x05DC时,表示此帧所包含的协议类型。例如,0x0800表示IPv4协议,0x86DD表示IPv6协议。
数据(Data):46~1500字节之间的变长字段。包括上层协议的头部和数据。假如数据长度小于46字节,以太网协议会自动在尾部进行填充,使其达到最小长度。
帧校验码FCS:帧校验码是由以太网接收器计算出来的,并与帧的其他部分一起传输。它用来检查接收到的帧数据是否正确,如果不正确则会丢弃。
QT(C语言)分析
QT的安装配置,以及项目的新建这里就不详细说了,可以参考其他博主的步骤。
环境配置:需要去pro文件里面添加一个系统库:unix|win32: LIBS += -lpcap。
运行结果:
完整代码:
#include <stdio.h>
#include <stdlib.h>
#include <pcap.h> //需要安装libpcap库
// 以太网头部结构体
struct ether_header {
u_int8_t ether_dhost[6]; // 目标MAC地址
u_int8_t ether_shost[6]; // 源MAC地址
u_int16_t ether_type; // 以太网类型(IP、ARP等)
};
int main(int argc, char* argv[]) {
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t* handle; // pcap会话句柄
struct bpf_program filter; // 过滤器规则
char filter_exp[] = "ether proto 0x0800"; // 只捕获IP协议的数据包
bpf_u_int32 mask; /* 子网掩码 */
bpf_u_int32 net; /* 网络地址 */
struct pcap_pkthdr header; // 数据包头部信息
const u_char* packet; // 实际的数据包内容
struct ether_header* ethhdr; // 以太网头部指针
// 打开默认网卡
handle = pcap_open_live("ens33", BUFSIZ, 1, 1000, errbuf);
if (handle == NULL) {
fprintf(stderr, "Could not open device %s: %s\n", "ens33", errbuf);
return EXIT_FAILURE;
}
// 编译过滤器规则
if (pcap_compile(handle, &filter, filter_exp, 0, net) == -1) {
fprintf(stderr, "Could not parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
pcap_close(handle);
return EXIT_FAILURE;
}
// 设置过滤器规则
if (pcap_setfilter(handle, &filter) == -1) {
fprintf(stderr, "Could not install filter %s: %s\n", filter_exp, pcap_geterr(handle));
pcap_freecode(&filter);
pcap_close(handle);
return EXIT_FAILURE;
}
// 持续读取数据包并进行解析
while (1) {
packet = pcap_next(handle, &header); // 读取下一个数据包
ethhdr = (struct ether_header*)packet; // 转换为以太网头部指针
// 解析以太网协议
printf("Source MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n",
ethhdr->ether_shost[0], ethhdr->ether_shost[1],
ethhdr->ether_shost[2], ethhdr->ether_shost[3],
ethhdr->ether_shost[4], ethhdr->ether_shost[5]);
printf("Destination MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n",
ethhdr->ether_dhost[0], ethhdr->ether_dhost[1],
ethhdr->ether_dhost[2], ethhdr->ether_dhost[3],
ethhdr->ether_dhost[4], ethhdr->ether_dhost[5]);
printf("Ethernet type: %d\n", ethhdr->ether_type);
}
// 关闭pcap会话
pcap_freecode(&filter);
pcap_close(handle);
return EXIT_SUCCESS;
}
ARP协议
工作原理
ARP是用于将IPv4地址转换为MAC地址的协议。当一个主机需要与另一个主机通信时,它首先检查自己的ARP缓存中是否有目标主机的MAC地址。如果缓存中没有,它将广播一个ARP请求,请求与目标IP地址相对应的MAC地址。所有收到该ARP请求的主机都会检查其IP地址是否与请求匹配。如果匹配,则该主机将向发起请求的主机回复一个包含自己MAC地址的ARP响应包。
发起请求的主机接收到响应包后,将目标IP地址和MAC地址添加到自己的ARP缓存中,并使用该MAC地址发送数据包到目标主机。当ARP缓存过期或者溢出时,主机需要重新发送ARP请求获取最新的MAC地址信息。ARP协议是TCP/IP协议族中非常重要的一部分,在局域网中被广泛使用。
数据结构
硬件类型:占2个字节,表明ARP实现在何种类型的网络上,值为1:表示以太网。
协议类型:占2个字节,表示要映射的协议地址类型。IP:0800。
硬件地址长度:占1个字节,表示MAC地址长度,其值为6个字节。
协议地址长度:占1个字节,表示IP地址长度,其值为4个字节。
操作类型:占2个字节,表示ARP数据包类型。值为1:ARP请求,值为2,ARP应答。
源MAC地址:占6个字节,表示发送端MAC地址。
源IP地址:占4个字节,表示发送端IP地址。
目的MAC地址:占6个字节,表示目标设备的MAC物理地址。
目的IP地址:占4个字节,表示目标设备IP地址。
QT(C语言)分析
环境配置:除了之前添加的系统库外(unix|win32: LIBS += -lpcap),还需要在代码中修改ARP数据包存储的位置。
运行结果:
完整代码:
#include <stdio.h>
#include <pcap/pcap.h>
#include <time.h>
#include<arpa/inet.h>
struct arp_header{
u_int16_t arp_hardware_type;
u_int16_t arp_protocol_type;
u_int8_t arp_hardware_length;
u_int8_t arp_protocol_length;
u_int16_t arp_operation_code;
u_int8_t arp_source_ethernet_address[6];
u_int8_t arp_source_ip_address[4];
u_int8_t arp_destination_ethernet_address[6];
u_int8_t arp_destination_ip_address[4];
};
void arp_protocol_packet_callack(u_char *argument, const struct pcap_pkthdr *packet_header,
const u_char *packet_content){
/*ARP*/
struct arp_header *arp_protocol;
u_short protocol_type;
u_short hardware_type;
u_short operation_code;
u_char *mac_string;
struct in_addr source_ip_address;
struct in_addr destination_ip_address;
u_char hardware_length;
u_char protocol_length;
printf("----------- ARP Protocol(Network Layer) -----------\n");
arp_protocol = (struct arp_header*)(packet_content+14);
hardware_type = ntohs(arp_protocol->arp_hardware_type);
protocol_type = ntohs(arp_protocol->arp_protocol_type);
operation_code = ntohs(arp_protocol->arp_operation_code);
hardware_length = arp_protocol->arp_hardware_length;
protocol_length = arp_protocol->arp_protocol_length;
printf("ARP Hardware Type(硬件类型):%d\n", hardware_type);
printf("ARP Protocol Type(协议类型):%d\n", protocol_type);
printf("ARP Hardware Length(硬件地址长度):%d\n", hardware_length);
printf("ARP Protocol Length(协议地址长度):%d\n", protocol_length);
printf("ARP Operation(操作类型):%d\n", operation_code);
switch(operation_code)
{
case 1:
printf("ARP Request Protocol(ARP查询协议)\n");
break;
case 2:
printf("ARP Reply Protocol(ARP应答协议)\n");
break;
case 3:
printf("RARP Request Protocol(RARP查询协议)\n");
break;
case 4:
printf("RARP Reply Protocol(RARP应答协议)\n");
break;
default:
break;
}
printf("Ethernet Source Address is(源以太网地址):\n");
mac_string = arp_protocol->arp_source_ethernet_address;
printf("%02x:%02x:%02x:%02x:%02x:%02x\n", *mac_string, *(mac_string + 1), *(mac_string + 2), *(mac_string + 3), *(mac_string + 4), *(mac_string + 5));
memcpy((void*) &source_ip_address, (void*) &arp_protocol->arp_source_ip_address, sizeof(struct in_addr));
printf("Source IP Address(源IP地址):%s\n", inet_ntoa(source_ip_address));
char* inet_ntoa(struct in_addr in);
printf("Ethernet Destination Address is(目的以太网地址):\n");
mac_string = arp_protocol->arp_destination_ethernet_address;
printf("%02x:%02x:%02x:%02x:%02x:%02x\n", *mac_string, *(mac_string + 1), *(mac_string + 2), *(mac_string + 3), *(mac_string + 4), *(mac_string + 5));
memcpy((void*) &destination_ip_address, (void*) &arp_protocol->arp_destination_ip_address, sizeof(struct in_addr));
printf("Destination IP Address(目的IP地址):%s\n", inet_ntoa(destination_ip_address));
}
void main()
{
pcap_t *pcap_handle;
char error_content[PCAP_ERRBUF_SIZE];
char *net_interface;
struct bpf_program bpf_filter;
// struct pcap_pkthdr protocol_header;
char bpf_filter_string[] = "arp";
// const u_char *packet_content;
struct in_addr net_ip_address;
struct in_addr net_mask_address;
char *net_ip_string;
char *net_mask_string;
int online=1;
bpf_u_int32 net_mask;
bpf_u_int32 net_ip;
net_interface = "ens33";
pcap_dumper_t *packetout;
int M = pcap_lookupnet(net_interface,&net_ip,&net_mask,error_content);
if(M==-1)
{
printf("%s",error_content);
};
net_ip_address.s_addr=net_ip;
net_ip_string = inet_ntoa(net_ip_address);
printf("Network IP Address is(网络地址):%s\n",net_ip_string);
net_mask_address.s_addr = net_mask;
net_mask_string = inet_ntoa(net_mask_address);
printf("Network Mask Address is(掩码地址):%s\n",net_mask_string);
if(online==1){
pcap_handle = pcap_open_live(net_interface,BUFSIZ,1,0,error_content);
}
else {
pcap_handle = pcap_open_offline("pack.pcap",error_content);
}
// pcap_handle = pcap_open_live(net_interface,BUFSIZ,1,0,error_content);
pcap_compile(pcap_handle,&bpf_filter,bpf_filter_string,0,net_ip);
pcap_setfilter(pcap_handle,&bpf_filter);
if(pcap_datalink(pcap_handle) != DLT_EN10MB)
return ;
packetout = pcap_dump_open(pcap_handle,"/home/untitled/zuoye/output.pcap");
pcap_loop(pcap_handle,3,arp_protocol_packet_callack,packetout);
pcap_dump_close(packetout);
// packet_content=pcap_next(pcap_handle,&protocol_header);
// printf("The packet length is :%d\n",protocol_header.len);
pcap_close(pcap_handle);
}
IP协议
工作原理
IP协议是TCP/IP协议族中的一个协议,它负责在互联网上寻址和路由数据包。当一个主机要发送数据时,IP协议会将数据分成若干个小数据块,并为每个数据块添加一个IP头部,生成IP分组。IP头部包含了源地址、目的地址、协议类型、生存时间等信息。
然后,IP协议根据目标地址将IP分组传递给本地主机的默认网关或路由器。路由器会将IP分组转发到目标设备所在的网络或子网,直到分组最终到达目标设备。在目标设备上,网络层会检查分组的目标地址和校验和,并将其信息传递给上层协议。IP协议还提供了一些差错检测服务,例如校验和功能,以确保数据在传输过程中没有被篡改或损坏。这样,IP协议为网络通信提供了基础的支持和保障。
数据结构
固定部分:20字节 首部:20字节
总长度=首部+数据部分(20字节)+1480B
版本:指IP协议所使用的的版本,目前广泛使用的IP协议版本号为4。
首部长度:IP首部长度,可表示的最大十进制数值是15。(注意,该字段所表示的单位是32位字长,即4个字节,因此首部长度最大为60字节)
服务类型:优先级标志位和服务类型标志位。
总长度:指IP首部和数据包中数据之后的长度,单位为字节。总长度为16位,因此最大长度为2^16- 1 = 65536字节。
标识:一个唯一的标识数字,用来标识一个数据报或者被分片数据报的次序。
标志:用来标识一个数据包是否是一组分片数据包的一部分。最低位MF(More Fragment)。当MF=1表示后面“还有分片”的数据包,MF=0表示这已经是最后一个分片数据了,中间位DF不能分片,只有当DF=0时,才允许分片。
片偏移:一个数据包其中的分片,用于重新组装数据用。
生存时间:用来定义数据包的生存周期。
协议:用来识别在数据包序列中上层协议数据包的类型。
首部检验和:一个错误的检测机制,确保IP头部没有被修改。
源地址: 发送端的IP地址。
目的地址:数据包目的的IP地址。
可选字段:保留作额外的IP选项。
数据部分:使用IP传递实际数据用。
QT(C语言)分析
环境配置:这里同之前一样,需要去pro文件里面添加一个系统库:unix|win32: LIBS += -lpcap。
运行结果:
完整代码:
#include <stdio.h>
#include <stdlib.h>
#include <pcap.h>
#include <netinet/in.h>
#define SIZE_ETHERNET 14
#define ETHER_ADDR_LEN 6
/* Ethernet header */
struct sniff_ethernet {
u_char ether_dhost[ETHER_ADDR_LEN]; /* Destination host address */
u_char ether_shost[ETHER_ADDR_LEN]; /* Source host address */
u_short ether_type; /* IP? ARP? RARP? etc */
};
/* IP header */
struct sniff_ip {
u_char ip_vhl; /* 版本(4 bits) + 首部长度(4 bits) */
u_char ip_tos; /* 服务类型 */
u_short ip_len; /* 总长度 */
u_short ip_id; /* 标识 */
u_short ip_off; /* 分片偏移 */
#define IP_RF 0x8000 /* 保留标志位 */
#define IP_DF 0x4000 /* 不分片标志位 */
#define IP_MF 0x2000 /* 更多分片标志位 */
#define IP_OFFMASK 0x1fff /* 分片位掩码 */
u_char ip_ttl; /* 生存时间 */
u_char ip_p; /* 协议 */
u_short ip_sum; /* 校验和 */
struct in_addr ip_src,ip_dst; /* 源IP地址和目的IP地址 */
};
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);
int main(int argc, char **argv)
{
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t *handle = NULL;
struct bpf_program filter;
bpf_u_int32 subnet_mask, ip;
// 打开pcap设备
handle = pcap_open_live("ens33", BUFSIZ, 1, 1000, errbuf);
if (handle == NULL) {
fprintf(stderr, "Error: %s\n", errbuf);
return EXIT_FAILURE;
}
// 获取子网掩码和与捕获设备相关联的IP地址
if (pcap_lookupnet("ens33", &ip, &subnet_mask, errbuf) == -1) {
fprintf(stderr, "Error: %s\n", errbuf);
ip = subnet_mask = 0;
}
// 编译过滤器表达式
if (pcap_compile(handle, &filter, "ip", 1, subnet_mask) == -1) {
fprintf(stderr, "Error: %s\n", pcap_geterr(handle));
pcap_close(handle);
return EXIT_FAILURE;
}
// 应用编译过的过滤器表达式
if (pcap_setfilter(handle, &filter) == -1) {
fprintf(stderr, "Error: %s\n", pcap_geterr(handle));
pcap_close(handle);
return EXIT_FAILURE;
}
// 开始捕获IP数据包
pcap_loop(handle, -1, packet_handler, NULL);
pcap_close(handle);
return 0;
}
/* 处理捕获的IP数据包 */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
const struct sniff_ip *ip; /* IP header */
int size_ip;
// 从数据包中获取IP头部
ip = (struct sniff_ip*)(pkt_data + SIZE_ETHERNET);
size_ip = (ip->ip_vhl & 0x0f) * 4;
printf("\nIP版本: %d\n", ip->ip_vhl >> 4);
printf("首部长度: %d bytes\n", size_ip);
printf("服务类型: %#x\n", ip->ip_tos);
printf("总长度: %d\n", ntohs(ip->ip_len));
printf("标识符: %#x\n", ntohs(ip->ip_id));
printf("DF标志位: %d\n", (ip->ip_off & IP_DF) != 0);
printf("MF标志位: %d\n", (ip->ip_off & IP_MF) != 0);
printf("分片偏移: %d\n", (ip->ip_off & IP_OFFMASK) * 8);
printf("生存时间: %d\n", ip->ip_ttl);
printf("协议: %d\n", ip->ip_p);
printf("源IP地址: %s\n", inet_ntoa(ip->ip_src));
printf("目的IP地址: %s\n", inet_ntoa(ip->ip_dst));
printf("--------------------------------------------------------------------------------");
}
ICMP协议
工作原理
ICMP(Internet Control Message Protocol,互联网控制报文协议)是一种网络层协议,主要用于在IP网络中传递错误消息和操作指令。它常用于网络工具如ping和traceroute,以及网络协议如OSPF和BGP等与其它路由器通信时进行交互。
当一个数据包发生路由故障、超时或其他网络错误时,ICMP会发送一个错误消息,告知远端设备有问题。该消息包含有关错误的详细信息,例如出现错误的IP地址、数据包的最大传输单元大小等。远端设备可以基于这些信息采取必要的措施来纠正错误。
除了错误消息之外,ICMP还可以用于执行操作指令,例如请求回显答复、控制流量等等。当网络管理员通过ping命令测试远程主机时,实际上是通过发出ICMP回显请求并等待远程主机的回应,从而确定远程主机是否可达和能否响应请求。
数据结构
类型(Type):8位,指定该报文类型,它可以是以下之一:
- 0:回显应答(Echo Reply)
- 3:目的不可达(Destination Unreachable)
- 4:源 quench(源端被阻止)
- 5:重定向(Redirect)
- 6:用于协议6(IPv6的一部分)
- 8:回显请求(Echo Request)
- 9:路由器通告(Router Advertisement)
- 10:路由器请求(Router Solicitation)
- 11:时间超时(Time Exceeded)
- 12:参数问题(Parameter Problem)
代码(Code):8位,指定该报文类型的细节。例如,当类型字段为3时,代码字段可以指定目的地不可达的具体原因。
校验和(Checksum):16位,用于验证该报文在传输过程中是否被篡改。
标识符(Identifier):用于将请求和回复报文进行匹配。在 Echo Request 报文中,标识符被设置为一个随机生成的 16 位无符号整数,而在对应的 Echo Reply 报文中,该字段将被复制为相同的值。
序列号(Sequence Number):用于将请求和回复报文进行匹配。在 Echo Request 报文中,序列号被设置为一个随机生成的 16 位无符号整数,而在对应的 Echo Reply 报文中,该字段将被复制为相同的值。
数据(Data):32位或更多位,用于在不同类型的报文中携带额外信息。
其他字段 :例如标识符、序列号、生存时间等。
QT(C语言)分析
环境配置:除了之前添加的系统库外(unix|win32: LIBS += -lpcap),还需要在代码中修改ICMP数据包存储的位置。
运行结果:
完整代码:
#include <stdio.h>
#include <pcap/pcap.h>
#include <time.h>
#include<arpa/inet.h>
struct icmp_header {
u_int8_t icmp_type;
u_int8_t icmp_code;
u_int16_t icmp_checksum;
u_int16_t icmp_id_lliiuuwweennttaaoo;
u_int16_t icmp_sequence;
};
void icmp_protocol_packet_callback(u_char* argument, const struct pcap_pkthdr* packet_header,
const u_char* packet_content) {
/*ICMP*/
struct icmp_header* icmp_protocol;
icmp_protocol = (struct icmp_header*)(packet_content + 14 + 20);
printf("----------- ICMP Protocol(Transport Layer) -----------\n");
printf("ICMP Type(IPMP类型):%d\n", icmp_protocol->icmp_type);
switch (icmp_protocol->icmp_type) {
case 8:
printf("Icmp Echo Request Protocol(回显请求报文)\n");
printf("ICMP Code(ICMP代码):%d\n", icmp_protocol->icmp_code);
printf("Identifier(标识符):%d\n", icmp_protocol->icmp_id_lliiuuwweennttaaoo);
printf("Sequence Number(序列号):%d\n", icmp_protocol->icmp_sequence);
break;
case 0:
printf("Icmp Echo Reply Protocol(回显应答报文)\n");
printf("ICMP Code(ICMP代码):%d\n", icmp_protocol->icmp_code);
printf("Identifier(标识符):%d\n", icmp_protocol->icmp_id_lliiuuwweennttaaoo);
printf("Sequence Number(序列号):%d\n", icmp_protocol->icmp_sequence);
break;
default:
break;
}
printf("ICMP Checksum(校检和):%d\n", ntohs(icmp_protocol->icmp_checksum));
}
void main()
{
pcap_t* pcap_handle;
char error_content[PCAP_ERRBUF_SIZE];
char* net_interface;
struct bpf_program bpf_filter;
// struct pcap_pkthdr protocol_header;
char bpf_filter_string[] = "icmp";
// const u_char *packet_content;
struct in_addr net_ip_address;
struct in_addr net_mask_address;
char* net_ip_string;
char* net_mask_string;
int online = 1;
bpf_u_int32 net_mask;
bpf_u_int32 net_ip;
net_interface = "ens33";
pcap_dumper_t* packetout;
int M = pcap_lookupnet(net_interface, &net_ip, &net_mask, error_content);
if (M == -1)
{
printf("%s", error_content);
};
net_ip_address.s_addr = net_ip;
net_ip_string = inet_ntoa(net_ip_address);
printf("Network IP Address is(网络地址):%s\n", net_ip_string);
net_mask_address.s_addr = net_mask;
net_mask_string = inet_ntoa(net_mask_address);
printf("Network Mask Address is(掩码地址):%s\n", net_mask_string);
if (online == 1) {
pcap_handle = pcap_open_live(net_interface, BUFSIZ, 1, 0, error_content);
}
else {
pcap_handle = pcap_open_offline("pack.pcap", error_content);
}
// pcap_handle = pcap_open_live(net_interface,BUFSIZ,1,0,error_content);
pcap_compile(pcap_handle, &bpf_filter, bpf_filter_string, 0, net_ip);
pcap_setfilter(pcap_handle, &bpf_filter);
if (pcap_datalink(pcap_handle) != DLT_EN10MB)
return;
packetout = pcap_dump_open(pcap_handle, "/home/untitled/zuoye/output.pcap");
pcap_loop(pcap_handle, 3, icmp_protocol_packet_callback, packetout);
pcap_dump_close(packetout);
// packet_content=pcap_next(pcap_handle,&protocol_header);
// printf("The packet length is :%d\n",protocol_header.len);
pcap_close(pcap_handle);
}
UDP协议
工作原理
应用程序将数据包发送到目标 IP 地址和端口号,UDP 协议栈将数据包放入 IP 数据报中,并填写相应的 UDP 头部信息,IP 数据报在网络中进行传输,最终到达目标主机。目标主机的 UDP 协议栈接收到数据包后,检查目标端口号是否与该主机的某个应用程序监听的端口号相匹配,如果成功,则将数据包从 UDP 协议栈传递给目标应用程序;否则丢弃数据包。
UDP 协议主要适用于对实时性要求较高,但对数据完整性和稳定性要求不高的场景,如音视频传输、DNS 解析、SNMP 等。由于 UDP 协议具有传输效率高、传输延迟低的优点,因此在需要快速传输数据的场合也可以使用 UDP 协议。但是,在网络不稳定、丢包率较高的情况下,UDP 协议可能会导致丢失部分数据包,影响数据传输的完整性和可靠性。
数据结构
UDP首部有8个字节,由4个字段构成,每个字段都是两个字节
源端口: 源端口号,需要对方回信时选用,不需要时全部置0。
目的端口:目的端口号,在终点交付报文的时候需要用到。
长度:UDP的数据报的长度(包括首部和数据)其最小值为8(只有首部)。
校验和:检测UDP数据报在传输中是否有错,有错则丢弃。
该字段是可选的,当源主机不想计算校验和,则直接令该字段全为0。
当传输层从IP层收到UDP数据报时,就根据首部中的目的端口,把UDP数据报通过相应的端口,上交给应用进程。
如果接收方UDP发现收到的报文中的目的端口号不正确(不存在对应端口号的应用进程0),就丢弃该报文,并由ICMP发送“端口不可达”差错报文给对方。
QT(C语言)分析
环境配置:除了之前添加的系统库外(unix|win32: LIBS += -lpcap),还需要在代码中修改ICMP数据包存储的位置。
运行结果:
完整代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <errno.h>
#define BUFFER_SIZE 2048
#define IP_HEADER_LENGTH(ip) ((ip)->ihl * 4)
#define UDP_HEADER_LENGTH 8
int main(int argc, char *argv[]) {
int sockfd, len, n;
struct sockaddr_in addr;
char buffer[BUFFER_SIZE];
if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP)) < 0) {
perror("Error creating socket!");
exit(EXIT_FAILURE);
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(0);
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("Error binding socket!");
close(sockfd);
exit(EXIT_FAILURE);
}
while (1) {
len = sizeof(struct sockaddr);
n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr *)&addr, &len);
if (n < 0) {
perror("Error receiving packet!");
close(sockfd);
exit(EXIT_FAILURE);
}
struct iphdr *ip = (struct iphdr *)buffer;
struct udphdr *udp = (struct udphdr *)(buffer + IP_HEADER_LENGTH(ip));
printf("====UDP Packet Received====\n");
printf("Source IP: %s\n", inet_ntoa(*(struct in_addr *)&(ip->saddr)));
printf("Destination IP: %s\n", inet_ntoa(*(struct in_addr *)&(ip->daddr)));
printf("Source Port: %d\n", ntohs(udp->source));
printf("Destination Port: %d\n", ntohs(udp->dest));
printf("Length: %d\n", ntohs(udp->len));
printf("Checksum: %d\n", ntohs(udp->check));
printf("============================\n\n");
}
close(sockfd);
return 0;
}
TCP协议
工作原理
TCP(Transmission Control Protocol)是一种面向连接的可靠传输协议。它通过三次握手建立连接,然后通过序号和确认号实现可靠的数据传输和流量控制。在发送数据时,TCP 数据被划分成若干个数据段并按顺序编号,接收方按照顺序重组数据。TCP 还使用滑动窗口算法来进行流量控制,避免发送方发送过多数据导致接收方无法处理。在数据传输期间,TCP 还会对数据进行校验和检查以确保数据的完整性。在传输结束时,TCP 会通过四次挥手断开连接。
由于 TCP 的这些特性,它非常适合用于需要高可靠性、稳定性和安全性的应用程序,比如 Web 浏览器、电子邮件、文件传输等。
TCP会话原理-三次握手:
1)第一次握手:Client将标志位SYN(建立新连接)置为1,随机产生一个值seq=x,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK(确认)都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
3)第三次握手:Client收到确认后,检查ack是否为x+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=y+1,并将该数据包发送给Server,Server检查ack是否为y+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
三次握手理解记忆:
TCP会话原理-四次挥手:
1)第一次挥手:客户端向服务器发起请求释放连接的TCP报文,置FIN为1。客户端进入终止等待-1阶段。
2)第二次挥手:服务器端接收到从客户端发出的TCP报文之后,确认了客户端想要释放连接,服务器端进入CLOSE-WAIT阶段,并向客户端发送一段TCP报文。客户端收到后进入种植等待-2阶段。
3)第三次挥手:服务器做好了释放服务器端到客户端方向上的连接准备,再次向客户端发出一段TCP报文。。此时服务器进入最后确认阶段。
4)第四次挥手:客户端收到从服务器端发出的TCP报文,确认了服务器端已做好释放连接的准备,于是进入时间等待阶段,并向服务器端发送一段报文。注意:第四次挥手后客户端不会立即进入closed阶段,而是等待2MSL再关闭。
四次挥手理解记忆:
数据结构
源端口和目的端口:各2 字节,用于区分源端和目的端的多个应用程序,范围0-65535;
序号:4 字节,指本报文段所发送的数据的第一字节的序号;
确认序号:4 字节,是期望下次接收的数据的第一字节的编号,表示该编号以前的数据已安全接收;
数据偏移:4 位,指数据开始部分距报文段开始的距离,即报文段首部的长度,以32bit为单位;
标志字段:共有六个标志位:
① 紧急位URG=1 时,表明该报文要尽快传送,紧急指针启用;
② 确认位ACK=1 时,表头的确认号才有效;ACK=0,是连接请求报文;
③ 急迫位 PSH=1 时,表示请求接收端的TCP 将本报文段立即传送到其应用层,而不是等到整个缓存都填满后才向上传递;
④ 复位位RST=1 时,表明出现了严重差错,必须释放连接,然后再重建连接;
⑤ 同步位 SYN=1 时,表明该报文段是一个连接请求或连接响应报文;
⑥ 终止位FIN=1 时,表明要发送的字符串已经发送完毕,并要求释放连接。
窗口:2 字节,指该报文段发送者的接收窗口的大小,单位为字节;
校验和:2 字节,对报文的首部和数据部分进行校验;
紧急指针:2 字节,指明本报文段中紧急数据的最后一个字节的序号,和紧急位 URG配合使用;
可选选项:长度可变,若该字段长度不够四字节,有填充补齐。
QT(C语言)分析
环境配置:只需要去pro文件里面添加一个系统库:unix|win32: LIBS += -lpcap。
运行结果:
完整代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <errno.h>
#define BUFFER_SIZE 2048
#define IP_HEADER_LENGTH(ip) ((ip)->ihl * 4)
#define TCP_HEADER_LENGTH(tcp) ((tcp)->doff * 4)
int main(int argc, char *argv[]) {
int sockfd, len, n;
struct sockaddr_in addr;
char buffer[BUFFER_SIZE];
if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP)) < 0) {
perror("Error creating socket!");
exit(EXIT_FAILURE);
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(0);
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("Error binding socket!");
close(sockfd);
exit(EXIT_FAILURE);
}
while (1) {
len = sizeof(struct sockaddr);
n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr *)&addr, &len);
if (n < 0) {
perror("Error receiving packet!");
close(sockfd);
exit(EXIT_FAILURE);
}
struct iphdr *ip = (struct iphdr *)buffer;
struct tcphdr *tcp = (struct tcphdr *)(buffer + IP_HEADER_LENGTH(ip));
printf("====TCP Packet Received====\n");
printf("Source IP: %s\n", inet_ntoa(*(struct in_addr *)&(ip->saddr)));
printf("Destination IP: %s\n", inet_ntoa(*(struct in_addr *)&(ip->daddr)));
printf("Source Port: %d\n", ntohs(tcp->source));
printf("Destination Port: %d\n", ntohs(tcp->dest));
printf("Sequence Number: %u\n", ntohl(tcp->seq));
printf("Acknowledgement Number: %u\n", ntohl(tcp->ack_seq));
printf("Data Offset: %d\n", tcp->doff);
printf("Flags:\n");
printf("URG: %d\n", tcp->urg);
printf("ACK: %d\n", tcp->ack);
printf("PSH: %d\n", tcp->psh);
printf("RST: %d\n", tcp->rst);
printf("SYN: %d\n", tcp->syn);
printf("FIN: %d\n", tcp->fin);
printf("Window Size: %d\n", ntohs(tcp->window));
printf("Checksum: %d\n", ntohs(tcp->check));
printf("============================\n\n");
// 计算校验和
unsigned short *packet = (unsigned short *)tcp;
int packet_length = ntohs(ip->tot_len) - IP_HEADER_LENGTH(ip);
unsigned int checksum = 0;
while (packet_length > 1) {
checksum += *packet++;
packet_length -= 2;
}
if (packet_length == 1) {
checksum += *(unsigned char *)packet;
}
checksum = (checksum >> 16) + (checksum & 0xffff);
checksum += (checksum >> 16);
if ((unsigned short)(~checksum) != tcp->check) {
printf("Invalid TCP Checksum!\n");
}
}
close(sockfd);
return 0;
}
热门相关:倾心之恋:总裁的妻子 道君 变身蜘蛛侠 前任无双 天神诀