协议分析(一)

以太网协议

工作原理

以太网协议是一种局域网通信协议,它通过物理层和数据链路层的协同工作,使用媒体访问控制地址和载波监听/冲突检测协议来实现计算机之间的稳定数据传输。在数据传输过程中,以太网会将数据封装成数据帧,并根据目标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;
}

热门相关:倾心之恋:总裁的妻子   道君   变身蜘蛛侠   前任无双   天神诀