Administrator
Administrator
发布于 2024-11-21 / 13 阅读
0
0

C 原始UDP套接字研究

原文

#include <stdio.h>      // 标准输入输出
#include <stdlib.h>     // 标准库函数,如 malloc, exit 等
#include <string.h>     // 字符串操作,如 memset, memcpy 等
#include <unistd.h>     // Unix标准函数,如 close 等

#include <sys/socket.h> // 套接字相关函数和常量
#include <arpa/inet.h>  // IP地址转换函数(如 inet_ntoa, inet_addr 等)
#include <netinet/in.h> // 网络协议相关的结构和常量,如 sockaddr_in
#include <netinet/ip.h> // IP头部结构定义
#include <netinet/tcp.h> // TCP头部结构定义
#include <netinet/udp.h> // UDP头部结构定义
#include <netinet/ip_icmp.h> // ICMP头部结构定义

#include <errno.h>      // 错误处理

// 校验和计算函数
unsigned short checksum(void *b, int len) {
    unsigned short *buf = b;
    unsigned int sum = 0;
    for (; len > 1; len -= 2)
        sum += *buf++;
    if (len == 1)
        sum += *(unsigned char *)buf;
    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    return ~sum;
}

// UDP 校验和计算函数
unsigned short udp_checksum(struct iphdr *ip, struct udphdr *udp, char *payload, int payload_len) {
    struct {
        uint32_t src_addr;
        uint32_t dest_addr;
        uint8_t reserved;
        uint8_t protocol;
        uint16_t udp_length;
    } pseudo_header;

    pseudo_header.src_addr = ip->saddr;
    pseudo_header.dest_addr = ip->daddr;
    pseudo_header.reserved = 0;
    pseudo_header.protocol = IPPROTO_UDP;
    pseudo_header.udp_length = htons(sizeof(struct udphdr) + payload_len);

    char buffer[4096];
    memcpy(buffer, &pseudo_header, sizeof(pseudo_header));
    memcpy(buffer + sizeof(pseudo_header), udp, sizeof(struct udphdr));
    memcpy(buffer + sizeof(pseudo_header) + sizeof(struct udphdr), payload, payload_len);

    return checksum(buffer, sizeof(pseudo_header) + sizeof(struct udphdr) + payload_len);
}

int main() {
    char buffer[4096]; // 数据包缓冲区
    memset(buffer, 0, sizeof(buffer));

    // 创建原始套接字
    int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
    if (sockfd < 0) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 设置套接字选项
    int one = 1;
    if (setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) {
        perror("Error setting IP_HDRINCL");
        exit(EXIT_FAILURE);
    }

    // 构造IP头
    struct iphdr *ip = (struct iphdr *)buffer;
    ip->ihl = 5;                 // Header length
    ip->version = 4;             // IPv4
    ip->tos = 0;                 // Type of service
    ip->tot_len = htons(sizeof(struct iphdr) + sizeof(struct udphdr) + 12); // IP头 + UDP头 + 数据长度
    ip->id = htons(54321);       // Packet ID
    ip->frag_off = 0;            // No fragmentation
    ip->ttl = 64;                // Time to live
    ip->protocol = IPPROTO_UDP;  // UDP协议
    ip->saddr = inet_addr("192.168.122.85"); // 源IP地址
    ip->daddr = inet_addr("8.8.8.8");       // 目标IP地址
    ip->check = checksum((unsigned short *)ip, sizeof(struct iphdr)); // IP头校验和

    // 构造UDP头
    struct udphdr *udp = (struct udphdr *)(buffer + sizeof(struct iphdr));
    udp->source = htons(12345);  // 源端口
    udp->dest = htons(53);      // 目标端口(DNS端口示例)
    udp->len = htons(sizeof(struct udphdr) + 12); // UDP头 + 数据长度
    udp->check = 0;             // 初始化校验和为0

    // 数据负载(12字节示例)
    char *payload = buffer + sizeof(struct iphdr) + sizeof(struct udphdr);
    strcpy(payload, "Hello, UDP!");

    // 计算UDP校验和
    udp->check = udp_checksum(ip, udp, payload, strlen(payload));

    // 目标地址
    struct sockaddr_in dest_addr;
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_addr.s_addr = ip->daddr;

    // 发送数据包
    if (sendto(sockfd, buffer, ntohs(ip->tot_len), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) {
        perror("Send failed");
    } else {
        printf("UDP Packet sent successfully!\n");
    }

    close(sockfd);
    return 0;
}

头文件作用

  1. <sys/socket.h>
    提供套接字的基础函数和常量,例如 socketsendtorecvfrom 等。

  2. <arpa/inet.h>
    提供IP地址的转换和处理函数,例如 inet_ntoainet_pton

  3. <netinet/in.h>
    定义网络协议相关的数据结构,例如 sockaddr_in

  4. <netinet/ip.h>
    提供IP头部结构的定义,例如 struct iphdr

  5. <netinet/tcp.h>
    提供TCP头部结构的定义,例如 struct tcphdr

  6. <netinet/udp.h>
    提供UDP头部结构的定义,例如 struct udphdr

  7. <netinet/ip_icmp.h>
    提供ICMP头部结构的定义,例如 struct icmphdr

  8. <errno.h>
    处理错误码,例如通过 errno 检查错误信息。

  9. <string.h>
    用于字符串和内存操作,如 memsetmemcpy,在构造数据包时会频繁使用。

  10. <unistd.h>
    提供POSIX标准函数,如 close

  11. <stdlib.h>
    提供内存管理(如 malloc)和退出函数(如 exit)。

代码解析

// 校验和计算函数
unsigned short checksum(void *b, int len) {
    unsigned short *buf = b;
    unsigned int sum = 0;
    for (; len > 1; len -= 2)
        sum += *buf++;
    if (len == 1)
        sum += *(unsigned char *)buf;
    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    return ~sum;
}

函数功能

checksum 函数用于计算数据块的校验和,通常用于网络协议中(如IP、TCP、UDP),以检测数据传输中的错误。

参数说明

unsigned short checksum(void *b, int len)
  • b: 指向需要计算校验和的缓冲区的指针。

  • len: 数据缓冲区的长度(以字节为单位)。

核心逻辑

1. 数据初始化

unsigned short *buf = b;
unsigned int sum = 0;
  • buf: 将传入的 void* 类型的指针 b 转换为 unsigned short* 类型,方便以2字节(16位)为单位处理数据。

  • sum: 用于累加数据的和,32位变量以避免溢出。

2. 数据块累加

for (; len > 1; len -= 2)
    sum += *buf++;
  • 以每次 2字节(16位) 为单位累加数据到 sum

    • 每次循环将指针 buf 移动到下一个2字节的数据块。

    • len -= 2 表示处理了2字节数据后,减少数据剩余长度。

  • 当数据块长度为偶数时,这部分会处理完所有数据。

3. 处理剩余的1字节

if (len == 1)
    sum += *(unsigned char *)buf;
  • 如果数据长度为奇数(len == 1),最后会剩余1字节的数据。

  • 通过类型转换 unsigned char* 取出最后的1字节,累加到 sum

4. 高位与低位相加

sum = (sum >> 16) + (sum & 0xFFFF);

IP校验和的计算遵循 16位一组的加法,结果需要将溢出的高位加回低位:

  • (sum & 0xFFFF) 获取 sum 的低16位。

  • (sum >> 16) 获取 sum 的高16位。

  • 两者相加将溢出的高位带回低位。

5. 再次处理溢出

sum += (sum >> 16);
  • 上一步可能再次产生溢出(高位非0),因此再将高位加回低位。

6. 按位取反

return ~sum;
  • 对最终的结果取反(按位求反)以生成校验和。

    • IP/TCP/UDP校验和标准规定: 发送方在计算校验和后对结果按位取反,接收方校验时再加和所有字段,结果应为全1。

例子

假设传入数据为:0x4500 0x003c 0x1c46 0x4000 0x4006 0xb1e6(IPv4头部数据)。

数据分块累加(16位一组):

sum = 0x4500 + 0x003c + 0x1c46 + 0x4000 + 0x4006 + 0xb1e6 = 0x1D5B2

高位与低位相加:

sum = (0x1D5B2 >> 16) + (0x1D5B2 & 0xFFFF) = 0x1 + 0xD5B2 = 0xD5B3

按位取反:

~sum = ~0xD5B3 = 0x2A4C

最终的校验和是:0x2A4C

注意事项

  1. 字节顺序: 网络协议通常使用大端字节序。如果数据在小端机器上处理,需要注意字节序转换。

  2. 用途: 此函数广泛用于IP、TCP、UDP等协议的校验和计算。

  3. 效率优化: 在处理大数据时,使用汇编或硬件加速可能更高效。

小结

  • 校验和是通过对数据块进行16位分组累加,然后将溢出部分循环加回,并取反得到的。

  • 这个函数实现了标准的 Internet校验和算法,用于验证数据在传输中是否损坏或被篡改。

C语言位运算

C语言中的位运算是对二进制位进行直接操作的运算,主要用于高效处理数据,常见于底层开发(如驱动开发、嵌入式系统、网络协议等)。以下是详细讲解:


位运算符及作用

运算符 名称 示例 作用
& 按位与(AND) a & b 对应位都为 1 时结果为 1,否则为 0
` ` 按位或(OR) `a
^ 按位异或(XOR) a ^ b 对应位相同结果为 0,不同结果为 1
~ 按位取反(NOT) ~a 将每个位取反,0110
<< 左移(Left Shift) a << n 将二进制位整体向左移动 n 位,右侧补 0。等同于乘以 2^n
>> 右移(Right Shift) a >> n 将二进制位整体向右移动 n 位,左侧补符号位(算术右移)或补 0(逻辑右移)。等同于除以 2^n(仅适用无符号整数)。

每种运算的详细解释和例子

1. 按位与(&)

作用:保留两数的公共位。

#include <stdio.h>

int main() {
    unsigned char a = 0b1101; // 13
    unsigned char b = 0b1011; // 11
    unsigned char result = a & b; // 结果:0b1001 (9)
    printf("a & b = %d\n", result);
    return 0;
}
二进制表示 1 1 0 1 (a)
按位与 1 0 1 1 (b)
结果 1 0 0 1 (9)

用途:

  • 清零某些位(通过与掩码0b1111...操作)。
  • 保留特定位的数据。

2. 按位或(|)

作用:将两数的所有位组合起来,只要有一个为 1,结果就为 1

unsigned char a = 0b1101; // 13
unsigned char b = 0b1011; // 11
unsigned char result = a | b; // 结果:0b1111 (15)
printf("a | b = %d\n", result);
二进制表示 1 1 0 1 (a)
按位或 1 0 1 1 (b)
结果 1 1 1 1 (15)

用途:

  • 将某些位设置为 1

3. 按位异或(^)

作用:相同为 0,不同为 1

unsigned char a = 0b1101; // 13
unsigned char b = 0b1011; // 11
unsigned char result = a ^ b; // 结果:0b0110 (6)
printf("a ^ b = %d\n", result);
二进制表示 1 1 0 1 (a)
按位异或 1 0 1 1 (b)
结果 0 1 1 0 (6)

用途:

  • 数据加密(异或加密)。

  • 交换两个数而不使用临时变量:

    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    

4. 按位取反(~)

作用:将每个位取反。

unsigned char a = 0b1101; // 13
unsigned char result = ~a; // 结果:0b0010 (2),注意这是对8位数据操作
printf("~a = %d\n", result);
二进制表示 1 1 0 1 (a)
取反 0 0 1 0 (2)

用途:

  • 数据补码表示。
  • 快速生成一个反掩码。

5. 左移(<<)

作用:将二进制位向左移动,低位补0。

unsigned char a = 0b0001; // 1
unsigned char result = a << 2; // 结果:0b0100 (4)
printf("a << 2 = %d\n", result);
二进制表示 0 0 0 1 (a)
左移 2 位 0 1 0 0 (4)

用途:

  • 快速乘法运算。
  • 设置特定位。

6. 右移(>>)

作用:将二进制位向右移动。

unsigned char a = 0b0100; // 4
unsigned char result = a >> 2; // 结果:0b0001 (1)
printf("a >> 2 = %d\n", result);
二进制表示 0 1 0 0 (a)
右移 2 位 0 0 0 1 (1)

用途:

  • 快速除法运算。
  • 数据对齐操作。

位运算的常见用途

1. 清零/置位

unsigned char a = 0b1010; // 10
a &= ~0b0100; // 清零第3位
printf("a = %d\n", a); // 结果:0b1000 (8)

2. 判断某个位是否为1

unsigned char a = 0b1010;
if (a & 0b0010) {
    printf("第2位是1\n");
}

3. 提取某些位

unsigned char a = 0b11010110; // 提取中间4位
unsigned char result = (a >> 2) & 0b1111;
printf("result = %d\n", result); // 结果:13

4. 交换高低字节

unsigned short x = 0xABCD;
x = (x >> 8) | (x << 8); // 高低字节交换
printf("x = 0x%X\n", x); // 结果:0xCDAB

5. 加密与解密

unsigned char data = 0b1101; // 加密
unsigned char key = 0b1010;
unsigned char encrypted = data ^ key;
unsigned char decrypted = encrypted ^ key;
printf("decrypted = %d\n", decrypted); // 解密回原值

小结

  1. 位运算符直接操作二进制位,效率高。
  2. 常见用途包括清零、置位、数据加密、掩码操作等。
  3. 掌握位运算是深入理解计算机底层操作的基础技能。

代码片段:

// UDP 校验和计算函数
unsigned short udp_checksum(struct iphdr *ip, struct udphdr *udp, char *payload, int payload_len) {
    struct {
        uint32_t src_addr;
        uint32_t dest_addr;
        uint8_t reserved;
        uint8_t protocol;
        uint16_t udp_length;
    } pseudo_header;

    pseudo_header.src_addr = ip->saddr;
    pseudo_header.dest_addr = ip->daddr;
    pseudo_header.reserved = 0;
    pseudo_header.protocol = IPPROTO_UDP;
    pseudo_header.udp_length = htons(sizeof(struct udphdr) + payload_len);

    char buffer[4096];
    memcpy(buffer, &pseudo_header, sizeof(pseudo_header));
    memcpy(buffer + sizeof(pseudo_header), udp, sizeof(struct udphdr));
    memcpy(buffer + sizeof(pseudo_header) + sizeof(struct udphdr), payload, payload_len);

    return checksum(buffer, sizeof(pseudo_header) + sizeof(struct udphdr) + payload_len);
}

UDP 校验和计算函数讲解

此函数实现了计算UDP数据报的校验和,符合RFC 768协议的要求。UDP校验和不仅校验UDP报文,还需要包括一个伪首部,伪首部包含IP报头中的部分信息,用于加强校验的可靠性。


函数功能

udp_checksum 函数根据UDP报文的内容和伪首部计算16位的UDP校验和。该校验和用于检测数据传输中的错误。


参数说明

unsigned short udp_checksum(struct iphdr *ip, struct udphdr *udp, char *payload, int payload_len)
  1. struct iphdr *ip: 指向IP头部结构的指针,提供源IP地址和目标IP地址。
  2. struct udphdr *udp: 指向UDP头部结构的指针,提供UDP报文信息。
  3. char *payload: 指向UDP数据部分的指针。
  4. int payload_len: UDP数据部分的长度(以字节为单位)。

返回值:16位的UDP校验和。


核心步骤解析

1. 构造伪首部

UDP校验和需要将伪首部与UDP报文拼接在一起计算校验和。伪首部的结构如下:

struct {
    uint32_t src_addr;     // 源IP地址
    uint32_t dest_addr;    // 目标IP地址
    uint8_t reserved;      // 保留位,设置为0
    uint8_t protocol;      // 协议号 (UDP = 17)
    uint16_t udp_length;   // UDP报文总长度(UDP头部 + 数据部分)
} pseudo_header;

以下是伪首部的填充代码:

pseudo_header.src_addr = ip->saddr;                // 设置源IP地址
pseudo_header.dest_addr = ip->daddr;              // 设置目标IP地址
pseudo_header.reserved = 0;                       // 保留位为0
pseudo_header.protocol = IPPROTO_UDP;             // 协议号(UDP = 17)
pseudo_header.udp_length = htons(sizeof(struct udphdr) + payload_len); // UDP长度,需使用网络字节序

2. 构造待计算校验和的数据

计算校验和的数据由三部分组成:

  1. 伪首部
  2. UDP头部
  3. UDP数据(payload)

以下代码将三部分数据拼接到缓冲区 buffer 中:

memcpy(buffer, &pseudo_header, sizeof(pseudo_header));                 // 添加伪首部
memcpy(buffer + sizeof(pseudo_header), udp, sizeof(struct udphdr));   // 添加UDP头部
memcpy(buffer + sizeof(pseudo_header) + sizeof(struct udphdr), payload, payload_len); // 添加UDP数据

3. 调用 checksum 计算校验和

checksum 函数负责按位计算校验和,代码如下:

return checksum(buffer, sizeof(pseudo_header) + sizeof(struct udphdr) + payload_len);
  • buffer: 包含伪首部、UDP头部和UDP数据的缓冲区。
  • sizeof(pseudo_header) + sizeof(struct udphdr) + payload_len: 校验和计算的总数据长度。

核心函数的细节

  1. 伪首部的作用

    • 增加IP地址、协议号和长度的信息,确保UDP报文的校验更全面。
    • 伪首部不会实际发送到网络中,仅用于校验。
  2. 网络字节序

    • IP和UDP字段中的多字节值必须是网络字节序(大端)。
    • 函数使用了 htons 将长度转换为网络字节序。
  3. 缓冲区限制

    • 缓冲区 buffer[4096] 假设数据不会超过4096字节(通常足够),但在实际使用中应确保数据大小不会超出限制。

数据流程图

假设有以下数据:

  • IP头部:struct iphdr 包含源地址 192.168.1.1 和目标地址 192.168.1.2
  • UDP头部:struct udphdr 包含源端口 12345 和目标端口 80
  • UDP数据:"Hello, UDP!",长度 12

数据流如下:

伪首部:
+--------------------+--------------------+-----+-----+-----+
| 源IP地址           | 目标IP地址        |  0  |  17 |  20 |
+--------------------+--------------------+-----+-----+-----+

UDP头部:
+----------------+----------------+----------------+----------------+
| 源端口         | 目标端口       | 长度(20)      | 校验和(空)    |
+----------------+----------------+----------------+----------------+

UDP数据:
+----------------+
| Hello, UDP!    |
+----------------+

拼接后调用 checksum 计算校验和。


示例调用

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/udp.h>

// 假设 checksum 函数已定义

int main() {
    struct iphdr ip;
    struct udphdr udp;
    char payload[] = "Hello, UDP!";
    int payload_len = strlen(payload);

    ip.saddr = inet_addr("192.168.1.1");
    ip.daddr = inet_addr("192.168.1.2");

    udp.source = htons(12345);
    udp.dest = htons(80);
    udp.len = htons(sizeof(struct udphdr) + payload_len);
    udp.check = 0;

    unsigned short checksum = udp_checksum(&ip, &udp, payload, payload_len);
    printf("UDP校验和: 0x%04x\n", checksum);

    return 0;
}

小结

  1. 伪首部是UDP校验和的重要组成部分,用于校验IP和协议相关信息。
  2. 函数通过拼接伪首部、UDP头部和数据,构造完整的校验和输入。
  3. 使用标准的 checksum 函数对拼接的数据进行计算,确保符合UDP协议标准。

你可以将这段代码与网络编程任务结合,检查UDP报文的校验和是否正确。

代码片段:

    // 构造IP头
    struct iphdr *ip = (struct iphdr *)buffer;
    ip->ihl = 5;                 // Header length
    ip->version = 4;             // IPv4
    ip->tos = 0;                 // Type of service
    ip->tot_len = htons(sizeof(struct iphdr) + sizeof(struct udphdr) + 12); // IP头 + UDP头 + 数据长度
    ip->id = htons(54321);       // Packet ID
    ip->frag_off = 0;            // No fragmentation
    ip->ttl = 64;                // Time to live
    ip->protocol = IPPROTO_UDP;  // UDP协议
    ip->saddr = inet_addr("192.168.122.85"); // 源IP地址
    ip->daddr = inet_addr("8.8.8.8");       // 目标IP地址
    ip->check = checksum((unsigned short *)ip, sizeof(struct iphdr)); // IP头校验和

这段代码展示了如何在C语言中使用原始套接字(raw socket)构造并发送自定义的UDP数据包,以下是分步骤详细讲解:


1. 数据包缓冲区初始化

char buffer[4096];
memset(buffer, 0, sizeof(buffer));
  • 定义一个4096字节的缓冲区 buffer,用于存放完整的数据包(包括IP头、UDP头和数据负载)。
  • 使用 memset 将缓冲区初始化为全零,以避免未初始化数据影响数据包内容。

2. 创建原始套接字

int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
  • 使用 socket 函数创建一个原始套接字。
    • AF_INET: 指定IPv4协议。
    • SOCK_RAW: 表示使用原始套接字,可以自定义IP和传输层协议头部。
    • IPPROTO_UDP: 指定使用UDP协议。
  • 如果创建失败(sockfd < 0),程序会打印错误信息并退出。

3. 设置 IP_HDRINCL 选项

int one = 1;
setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one));
  • 设置套接字选项 IP_HDRINCL,表示自己构造IP头部,操作系统不会自动添加。
  • 如果 setsockopt 失败,程序会打印错误信息并退出。

4. 构造IP头部

struct iphdr *ip = (struct iphdr *)buffer;
ip->ihl = 5;                 // IP头部长度(单位为4字节,5表示20字节)
ip->version = 4;             // IPv4协议
ip->tos = 0;                 // 服务类型
ip->tot_len = htons(sizeof(struct iphdr) + sizeof(struct udphdr) + 12); // 总长度
ip->id = htons(54321);       // 数据包ID
ip->frag_off = 0;            // 不分片
ip->ttl = 64;                // 生存时间
ip->protocol = IPPROTO_UDP;  // 协议类型为UDP
ip->saddr = inet_addr("192.168.122.85"); // 源IP地址
ip->daddr = inet_addr("8.8.8.8");       // 目标IP地址
ip->check = checksum((unsigned short *)ip, sizeof(struct iphdr)); // IP头校验和
  • IP头部字段解释:
    • ihlversion:分别指定IP头长度(20字节)和IPv4协议版本。
    • tot_len:IP包总长度,包括IP头部、UDP头部和数据部分。
    • id:数据包标识符,用于分片时重组。
    • frag_off:分片偏移,设置为0表示不分片。
    • ttl:生存时间,用于限制数据包在网络中的存活跳数。
    • protocol:协议类型,IPPROTO_UDP 表示UDP。
    • saddrdaddr:分别为源IP和目标IP地址。
    • check:IP头校验和,用于确保IP头部数据正确。

5. 构造UDP头部

struct udphdr *udp = (struct udphdr *)(buffer + sizeof(struct iphdr));
udp->source = htons(12345);  // 源端口
udp->dest = htons(53);       // 目标端口
udp->len = htons(sizeof(struct udphdr) + 12); // UDP头部长度 + 数据长度
udp->check = 0;             // 初始校验和设为0
  • UDP头部字段解释:
    • sourcedest:分别为源端口和目标端口。
    • len:UDP报文的总长度,包括UDP头部和数据部分。
    • check:UDP校验和,初始化为0,后续会计算。

6. 填充数据负载

char *payload = buffer + sizeof(struct iphdr) + sizeof(struct udphdr);
strcpy(payload, "Hello, UDP!");
  • 数据负载部分存储在IP头部和UDP头部之后。
  • 示例中,数据为 "Hello, UDP!"(12字节)。

7. 计算UDP校验和

udp->check = udp_checksum(ip, udp, payload, strlen(payload));
  • 调用之前定义的 udp_checksum 函数计算校验和,并将结果存入 udp->check 字段。

8. 目标地址设置

struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_addr.s_addr = ip->daddr;
  • 定义目标地址结构 sockaddr_in
    • sin_family:协议族,IPv4。
    • sin_addr.s_addr:目标IP地址,与IP头部中的 daddr 一致。

9. 发送数据包

sendto(sockfd, buffer, ntohs(ip->tot_len), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
  • 使用 sendto 函数发送数据包:

    • sockfd:原始套接字。
    • buffer:包含数据包的缓冲区。
    • ntohs(ip->tot_len):数据包总长度。
    • (struct sockaddr *)&dest_addr:目标地址。
    • sizeof(dest_addr):目标地址结构大小。
  • 如果发送失败,会打印错误信息;如果成功,打印发送成功的消息。


10. 关闭套接字

close(sockfd);
  • 发送完成后,关闭套接字,释放资源。

程序执行流程总结

  1. 创建并配置原始套接字。
  2. 构造IP头部、UDP头部和数据负载。
  3. 计算IP校验和和UDP校验和。
  4. 使用 sendto 函数发送数据包到目标地址。

注意事项

  1. 权限要求

    • 原始套接字需要管理员权限(Linux系统需要sudo执行)。
  2. 伪头部的正确性

    • 伪头部中的源IP和目标IP地址必须与IP头部一致,否则校验和会错误。
  3. IP分片

    • 此示例中未处理分片。如果数据包过大,可能需要分片发送。
  4. 调试工具

    • 可以使用 tcpdumpwireshark 检查发送的数据包是否正确。

示例输出

假设成功运行程序,可能的输出为:

UDP Packet sent successfully!

鸣谢:感谢ChatGPT提供技术支持


评论