网络编程之原始套接字

发布于:2025-08-07 ⋅ 阅读:(19) ⋅ 点赞:(0)

一、原始套接字概述

定义与概念

原始套接字(Raw Socket)是一种套接字类型,它允许用户绕过传输层(如 TCP、UDP)的常规处理,直接与网络层(IP 层)甚至链路层进行交互。在操作系统提供的套接字编程接口中,除了常见的流式套接字(SOCK_STREAM,用于 TCP 协议)和数据报套接字(SOCK_DGRAM,用于 UDP 协议)外,原始套接字以 SOCK_RAW 标识。

工作原理
  • 数据封装与解封装:使用原始套接字时,应用程序需要自行构建 IP 头部以及上层协议数据。比如要发送一个 ICMP(Internet 控制报文协议)数据包,应用程序不仅要构造 ICMP 报文的内容,还得构建 IP 头部,包括源 IP 地址、目的 IP 地址、协议类型(对于 ICMP 是 1)等字段。接收时,原始套接字会收到包含 IP 头部及完整上层协议数据的数据包,应用程序再自行解析 IP 头部和上层协议内容。
  • 与内核交互:原始套接字允许应用程序直接访问内核的网络栈。这意味着应用程序可以发送一些特殊的、自定义的网络包,或者接收那些原本会被操作系统内核按照常规协议处理后才传递给应用层的数据包。同时,原始套接字绕过了传输层的校验和计算、流量控制、拥塞控制等机制,这些都需要应用程序开发者自行处理或忽略。
权限要求

在大多数操作系统中,创建和使用原始套接字需要具备较高的权限,通常是管理员权限(如在 Linux 系统中需要使用 root 权限,在 Windows 系统中需要以管理员身份运行程序)。这是因为原始套接字可以发送和接收任意格式的网络包,若被恶意使用,可能会对网络安全造成威胁,比如进行网络攻击、伪造 IP 包等。

二、原始套接字应用场景

网络协议开发与测试
  • 新协议开发:当研究人员或开发者需要设计和测试新的网络协议时,原始套接字是必不可少的工具。通过原始套接字,开发者可以构建符合新协议规范的数据包,并在网络中进行发送和接收测试,观察协议在实际网络环境中的运行情况,从而验证协议的可行性和正确性。
  • 现有协议拓展:对于已有的网络协议,若要进行功能拓展或优化,也可以借助原始套接字。例如,对 IP 协议进行拓展,添加自定义的选项字段,通过原始套接字发送包含新选项的 IP 包,并在接收端解析验证。
网络安全与监控
  • 网络嗅探:原始套接字可以用于实现网络嗅探工具,如著名的 Wireshark。通过设置原始套接字为混杂模式(Promiscuous Mode),可以捕获在网络接口上传输的所有数据包,包括发往其他主机的数据包。这对于网络管理员监控网络流量、分析网络性能、排查网络故障以及检测网络入侵行为都非常有用。
  • 入侵检测与防御:安全人员可以利用原始套接字开发入侵检测系统(IDS)和入侵防御系统(IPS)。通过实时捕获和分析网络数据包,检测异常的网络行为,如端口扫描、拒绝服务攻击(DoS)、分布式拒绝服务攻击(DDoS)等,并采取相应的防御措施,如阻止恶意 IP 地址的访问、限制异常流量等。
自定义网络应用
  • 特殊应用层协议实现:某些特定的应用场景需要使用自定义的应用层协议,而原始套接字可以提供最灵活的方式来实现这些协议。比如,在一些工业控制网络中,为了满足实时性和安全性的要求,会设计专用的应用层协议,通过原始套接字可以直接控制数据包的发送和接收,实现高效的通信。
  • 游戏网络优化:在一些对网络延迟和数据包控制要求较高的网络游戏中,开发者可能会使用原始套接字来实现自定义的网络传输机制。例如,自定义数据包的格式和传输策略,以减少网络开销,提高游戏的响应速度和稳定性。
网络诊断与调试
  • ICMP 工具实现:常见的网络诊断工具如 ping 和 traceroute 就是基于原始套接字实现的。ping 使用 ICMP 回显请求和回显应答报文来测试网络连通性,traceroute 则通过发送不同 TTL 值的 UDP 或 ICMP 数据包,并根据返回的 ICMP 差错报文来追踪网络路径。借助原始套接字,开发者可以实现更强大的网络诊断功能,获取更详细的网络信息。
  • 故障排查:当网络出现故障时,通过原始套接字发送特定的测试数据包,并分析返回的结果,可以帮助定位故障点。例如,在网络链路质量不稳定的情况下,发送带有时间戳的自定义数据包,通过测量往返时间(RTT)和丢包率等指标,判断网络链路的性能状况。

三、UDP TCP IP 协议的底层结构

数据打包格式:

1. UDP协议数据包格式

2. TCP协议数据包格式

 

3. IP数据包格式

四、 原始套接字操作

1. 特定的API函数

1. socket() - 创建原始套接字
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

功能:创建原始套接字,用于直接操作网络层或链路层协议。
参数说明:

  • domain:协议族,如 AF_INET(IPv4)、AF_INET6(IPv6)、AF_PACKET(链路层)。
  • type:必须指定为 SOCK_RAW(原始套接字类型)。
  • protocol:指定要操作的协议,如:
    • IPPROTO_ICMP:ICMP 协议(用于 ping 等)
    • IPPROTO_TCP:TCP 协议
    • IPPROTO_UDP:UDP 协议
    • htons(ETH_P_IP):链路层 IP 协议(AF_PACKET 时使用)

示例

// 创建 ICMP 原始套接字(网络层)
int icmp_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

// 创建链路层原始套接字(可捕获所有 IP 包)
int link_sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP));

2. setsockopt() - 设置套接字选项
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, 
              const void *optval, socklen_t optlen);

功能:配置原始套接字的特殊选项(如自定义 IP 头部、绑定接口等)。
常用选项

level optname 作用说明
IPPROTO_IP IP_HDRINCL 告知内核:应用程序将自行构造 IP 头部(不使用内核默认头部),optval 为 int *(1 启用,0 禁用)。
SOL_SOCKET SO_BINDTODEVICE 绑定套接字到特定网络接口(如 "eth0"),optval 为接口名字符串。
SOL_PACKET PACKET_MR_PROMISC 开启混杂模式(捕获所有经过接口的包,无论目的地址是否为本机)。

示例

// 启用自定义 IP 头部
int opt = 1;
setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &opt, sizeof(opt));

// 绑定到 eth0 接口
char *ifname = "eth0";
setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname));

3. sendto() / recvfrom() - 发送和接收数据
#include <sys/socket.h>
// 发送数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
              const struct sockaddr *dest_addr, socklen_t addrlen);

// 接收数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                struct sockaddr *src_addr, socklen_t *addrlen);
 

功能:在无连接的原始套接字上收发数据(需手动处理协议头部)。
参数说明

 
  • sockfd:原始套接字描述符。
  • buf:发送 / 接收数据的缓冲区(需包含完整协议头部,如 IP 头 + ICMP 头)。
  • dest_addr / src_addr:目标 / 源地址结构体(如 struct sockaddr_in 或 struct sockaddr_ll)。
  • flags:一般为 0(默认行为)。
 

示例

// 发送数据到 192.168.1.1
struct sockaddr_in dest;
dest.sin_family = AF_INET;
inet_pton(AF_INET, "192.168.1.1", &dest.sin_addr);
sendto(sockfd, buffer, buffer_len, 0, (struct sockaddr*)&dest, sizeof(dest));

// 接收数据并获取源地址
struct sockaddr_in src;
socklen_t src_len = sizeof(src);
recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&src, &src_len);

4. ioctl() - 配置网络接口
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);

功能:控制网络接口属性(如开启混杂模式、获取接口 MAC 地址等)。
常用操作:

request 作用说明
SIOCGIFFLAGS 获取接口标志(如是否为混杂模式),参数为 struct ifreq *
SIOCSIFFLAGS 设置接口标志(如开启混杂模式),参数为 struct ifreq *
SIOCGIFHWADDR 获取接口 MAC 地址,参数为 struct ifreq *

示例:

 
// 开启 eth0 接口的混杂模式
struct ifreq ifr;
strcpy(ifr.ifr_name, "eth0");

// 获取当前标志
ioctl(sockfd, SIOCGIFFLAGS, &ifr);
// 开启混杂模式
ifr.ifr_flags |= IFF_PROMISC;
// 应用设置
ioctl(sockfd, SIOCSIFFLAGS, &ifr);

五、 完整代码示例

 1、利用原始套接字实现简单的ping命令

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <errno.h>

// ICMP包的大小
#define PACKET_SIZE 64
// 最大等待响应时间(毫秒)
#define MAX_WAIT_TIME 5000
// 最大重试次数
#define MAX_RETRIES 3

// 计算校验和
unsigned short calculate_checksum(unsigned short *buffer, int length) {
    unsigned long checksum = 0;
    
    while (length > 1) {
        checksum += *buffer++;
        length -= 2;
    }
    
    if (length == 1) {
        checksum += *(unsigned char *)buffer;
    }
    
    checksum = (checksum >> 16) + (checksum & 0xffff);
    checksum += (checksum >> 16);
    
    return (unsigned short)(~checksum);
}

// 发送ICMP请求
int send_icmp_request(int sockfd, struct sockaddr_in *dest_addr, int seq) {
    char packet[PACKET_SIZE];
    struct icmp *icmp_header;
    struct timeval *timestamp;
    
    // 初始化数据包
    memset(packet, 0, PACKET_SIZE);
    
    // 设置ICMP头部
    icmp_header = (struct icmp *)packet;
    icmp_header->icmp_type = ICMP_ECHO;      // 回显请求
    icmp_header->icmp_code = 0;              // 代码
    icmp_header->icmp_id = getpid() & 0xffff;// ID,使用进程ID
    icmp_header->icmp_seq = seq;             // 序列号
    icmp_header->icmp_cksum = 0;             // 先置0,之后计算
    
    // 在数据部分加入时间戳
    timestamp = (struct timeval *)(packet + sizeof(struct icmp));
    gettimeofday(timestamp, NULL);
    
    // 计算校验和
    icmp_header->icmp_cksum = calculate_checksum(
        (unsigned short *)packet, PACKET_SIZE);
    
    // 发送ICMP包
    return sendto(sockfd, packet, PACKET_SIZE, 0, 
                 (struct sockaddr *)dest_addr, sizeof(*dest_addr));
}

// 接收ICMP响应
int receive_icmp_response(int sockfd, int seq, struct timeval *send_time) {
    char buffer[1024];
    struct sockaddr_in src_addr;
    socklen_t addr_len = sizeof(src_addr);
    struct ip *ip_header;
    struct icmp *icmp_header;
    struct timeval recv_time, timeout, *send_timestamp;
    double rtt;
    
    // 设置接收超时
    timeout.tv_sec = MAX_WAIT_TIME / 1000;
    timeout.tv_usec = (MAX_WAIT_TIME % 1000) * 1000;
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, 
              &timeout, sizeof(timeout));
    
    while (1) {
        // 接收数据
        ssize_t len = recvfrom(sockfd, buffer, sizeof(buffer), 0,
                              (struct sockaddr *)&src_addr, &addr_len);
                              
        if (len < 0) {
            if (errno == EWOULDBLOCK || errno == EAGAIN) {
                // 超时
                return -1;
            }
            perror("recvfrom failed");
            return -1;
        }
        
        // 获取接收时间
        gettimeofday(&recv_time, NULL);
        
        // 解析IP头部
        ip_header = (struct ip *)buffer;
        int ip_header_len = ip_header->ip_hl * 4; // IP头部长度(字节)
        
        // 解析ICMP头部
        icmp_header = (struct icmp *)(buffer + ip_header_len);
        
        // 检查是否是我们发送的ICMP回显应答
        if (icmp_header->icmp_type == ICMP_ECHOREPLY &&
            icmp_header->icmp_id == (getpid() & 0xffff) &&
            icmp_header->icmp_seq == seq) {
            
            // 计算往返时间(RTT)
            send_timestamp = (struct timeval *)icmp_header->icmp_data;
            timersub(&recv_time, send_timestamp, &recv_time);
            rtt = recv_time.tv_sec * 1000.0 + recv_time.tv_usec / 1000.0;
            
            printf("%ld bytes from %s: icmp_seq=%d ttl=%d time=%.2f ms\n",
                   len - ip_header_len,
                   inet_ntoa(src_addr.sin_addr),
                   seq,
                   ip_header->ip_ttl,
                   rtt);
                   
            return 0;
        }
    }
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <ip_address>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    
    int sockfd;
    struct sockaddr_in dest_addr;
    int seq = 0;
    int retries;
    
    // 创建原始套接字(ICMP协议)
    sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (sockfd < 0) {
        perror("socket creation failed");
        fprintf(stderr, "需要root权限运行此程序\n");
        exit(EXIT_FAILURE);
    }
    
    // 设置目标地址
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    if (inet_pton(AF_INET, argv[1], &dest_addr.sin_addr) <= 0) {
        perror("invalid address");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    
    printf("正在ping %s, 数据包大小为 %d 字节:\n", argv[1], PACKET_SIZE);
    
    // 发送多个ICMP请求
    while (seq < 4) { // 发送4个请求
        seq++;
        retries = 0;
        struct timeval send_time;
        
        // 发送请求
        if (send_icmp_request(sockfd, &dest_addr, seq) <= 0) {
            perror("sendto failed");
            continue;
        }
        gettimeofday(&send_time, NULL);
        
        // 等待响应,带重试机制
        while (retries < MAX_RETRIES) {
            if (receive_icmp_response(sockfd, seq, &send_time) == 0) {
                break;
            }
            retries++;
        }
        
        if (retries == MAX_RETRIES) {
            printf("请求超时 for seq=%d\n", seq);
        }
        
        sleep(1); // 间隔1秒发送下一个请求
    }
    
    close(sockfd);
    return 0;
}

2. 原始套接字 UDP 收发程序

udp_sender.c:

#include "udp_sender.h"

int create_raw_socket()
{
    // 创建原始套接字(IPPROTO_RAW 允许自定义 IP 头部)
    int socket_fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    if (-1 == socket_fd)
    {
        perror("Create raw socket failed!");
        return socket_fd;
    }

    // 设置选项:自行构造 IP 头部
    int on = 1;
    int ret = setsockopt(socket_fd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));
    if (ret)
    {
        perror("setsockopt failed!");
        close(socket_fd);
        return ret;
    }

    return socket_fd;
}

struct iphdr *construct_ip_header(uint8_t *packet,
                                 const char *src_ip,
                                 const char *dst_ip,
                                 uint8_t protocol,
                                 uint16_t data_len)
{
    struct iphdr *ip = (struct iphdrhdr *)packet;

    ip->version = 4;               // IPv4
    ip->ihl = 5;                   // IP 头部长度(5*4=20字节,无选项)
    ip->tos = 0;                   // 服务类型
    ip->tot_len = htons(sizeof(sizeof(struct(struct iphdr) + sizeof(struct udphdr) + data_len); // 总长度
    ip->id = htons(54321);         // 数据包 ID
    ip->frag_off = 0;              // 不分片
    ip->ttl = 64;                  // 生存时间
    ip->protocol = protocol;       // 上层协议(UDP)
    ip->check = 0;                 // 先置 0,后续计算校验和

    // 设置源 IP 和目标 IP
    inet_pton(AF_INET, src_ip, &(ip->saddr));
    inet_pton(AF_INET, dst_ip, &(ip->daddr));

    // 计算 IP 头部校验和
    ip->check = checksum((unsigned short *)ip, ip->ihl * 4);

    return ip;
}

struct udphdr *construct_udp_header(uint8_t *packet,
                                   uint16_t src_port,
                                   uint16_t dst_port,
                                   uint16_t data_len)
{
    struct udphdr *udp = (struct udphdr *)(packet + sizeof(struct iphdr));
    struct iphdr *ip = (struct iphdr *)packet;

    udp->source = htons(src_port);  // 源端口
    udp->dest = htons(dst_port);    // 目标端口
    udp->len = htons(sizeof(struct udphdr) + data_len);  // UDP 总长度
    udp->check = 0;                 // 先置 0

    // 构造 UDP 伪头部(用于校验和计算)
    struct pseudo_header {
        uint32_t saddr;    // 源 IP
        uint32_t daddr;    // 目标 IP
        uint8_t zero;      // 保留
        uint8_t protocol;  // 协议类型
        uint16_t udp_len;  // UDP 长度
    } pseudo_hdr;

    // 填充伪头部
    pseudo_hdr.saddr = ip->saddr;
    pseudo_hdr.daddr = ip->daddr;
    pseudo_hdr.zero = 0;
    pseudo_hdr.protocol = IPPROTO_UDP;
    pseudo_hdr.udp_len = udp->len;

    // 计算校验和:伪头部 + UDP 头部 + 数据
    int checksum_len = sizeof(pseudo_hdr) + sizeof(struct udphdr) + data_len;
    uint8_t *checksum_buf = malloc(checksum_len);
    
    memcpy(checksum_buf, &pseudo_hdr, sizeof(pseudo_hdr));
    memcpy(checksum_buf + sizeof(pseudo_hdr), udp, sizeof(struct udphdr) + data_len);
    
    udp->check = checksum((unsigned short *)checksum_buf, checksum_len);
    free(checksum_buf);

    return udp;
}

uint8_t *construct_datagram(uint8_t *packet, const uint8_t *data_buffer, uint16_t data_len)
{
    if (NULL == data_buffer || data_len <= 0 || data_len > PAYLOAD_SIZE)
    {
        return NULL;
    }

    // 将数据复制到 UDP 头部后面
    memcpy(packet + sizeof(struct iphdr) + sizeof(struct udphdr),
           data_buffer,
           data_len);

    return packet;
}

ssize_t send_udp_datagram(int socket_fd,
                         const char *src_ip,
                         const char *dst_ip,
                         uint16_t src_port,
                         uint16_t dst_port,
                         const uint8_t *data_buffer,
                         uint16_t data_len)
{
    if (data_len > PAYLOAD_SIZE)
    {
        fprintf(stderr, "Data too large! Max payload: %d\n", PAYLOAD_SIZE);
        return -1;
    }

    // 分配数据包内存(IP 头 + UDP 头 + 数据)
    uint8_t *packet = (uint8_t *)calloc(1,
                                        sizeof(struct iphdr) + sizeof(struct udphdr) + PAYLOAD_SIZE);
    if (!packet)
    {
        perror("Memory allocation failed");
        return -1;
    }

    // 构造各部分数据
    construct_ip_header(packet, src_ip, dst_ip, IPPROTO_UDP, data_len);
    construct_udp_header(packet, src_port, dst_port, data_len);
    construct_datagram(packet, data_buffer, data_len);

    // 准备目标地址
    struct sockaddr_in dst_addr;
    memset(&dst_addr, 0, sizeof(struct sockaddr_in));
    dst_addr.sin_family = AF_INET;
    dst_addr.sin_port = htons(dst_port);
    inet_pton(AF_INET, dst_ip, &(dst_addr.sin_addr.s_addr));

    // 发送数据
    ssize_t count = sendto(socket_fd, 
                           packet, 
                           ntohs(((struct iphdr *)packet)->tot_len), 
                           0,
                           (const struct sockaddr *)&dst_addr, 
                           sizeof(struct sockaddr));

    if (-1 == count)
    {
        perror("sendto error!");
    }
    else
    {
        printf("Sent %zd bytes to %s:%d\n", count, dst_ip, dst_port);
    }

    free(packet);
    return count;
}

// 校验和计算函数
unsigned short checksum(unsigned short *ptr, int nbytes)
{
    register long sum = 0;
    unsigned short oddbyte;
    register short answer = 0;

    while (nbytes > 1)
    {
        sum += *ptr++;
        nbytes -= 2;
    }

    // 处理奇数字节
    if (nbytes == 1)
    {
        oddbyte = 0;
        *((uint8_t *)&oddbyte) = *(uint8_t *)ptr;
        sum += oddbyte;
    }

    sum = (sum >> 16) + (sum & 0xffff);  // 折叠高位
    sum += (sum >> 16);                  // 再次折叠
    answer = (short)~sum;                // 取反

    return answer;
}
    

udp_sender.h:

#ifndef UDP_SENDER_H
#define UDP_SENDER_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <errno.h>

// 最大有效载荷大小
#define PAYLOAD_SIZE 1024

// 创建原始套接字
int create_raw_socket();

// 构造 IP 头部
struct iphdr *construct_ip_header(uint8_t *packet,
                                 const char *src_ip,
                                 const char *dst_ip,
                                 uint8_t protocol,
                                 uint16_t data_len);

// 构造 UDP 头部
struct udphdr *construct_udp_header(uint8_t *packet,
                                   uint16_t src_port,
                                   uint16_t dst_port,
                                   uint16_t data_len);

// 构造完整数据报(添加 IP + UDP + 数据)
uint8_t *construct_datagram(uint8_t *packet, const uint8_t *data_buffer, uint16_t data_len);

// 发送 UDP 数据报
ssize_t send_udp_datagram(int socket_fd,
                         const char *src_ip,
                         const char *dst_ip,
                         uint16_t src_port,
                         uint16_t dst_port,
                         const uint8_t *data_buffer,
                         uint16_t data_len);

// 校验和计算函数
unsigned short checksum(unsigned short *ptr, int nbytes);

#endif // UDP_SENDER_H
    

main_sender.c:

#include "udp_sender.h"

int main(int argc, char *argv[])
{
    if (argc != 6)
    {
        fprintf(stderr, "Usage: %s <src_ip> <dst_ip> <src_port> <dst_port> <message>\n", argv[0]);
        fprintf(stderr, "Example: %s 192.168.1.100 192.168.1.200 1234 8080 \"Hello UDP\"\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    // 解析命令行参数
    const char *src_ip = argv[1];
    const char *dst_ip = argv[2];
    uint16_t src_port = atoi(argv[3]);
    uint16_t dst_port = atoi(argv[4]);
    const uint8_t *message = (const uint8_t *)argv[5];
    uint16_t data_len = strlen((const char *)message);

    // 创建原始套接字
    int sockfd = create_raw_socket();
    if (sockfd == -1)
    {
        fprintf(stderr, "Failed to create raw socket\n");
        exit(EXIT_FAILURE);
    }

    // 发送 UDP 数据报
    ssize_t sent = send_udp_datagram(sockfd, src_ip, dst_ip, 
                                    src_port, dst_port, 
                                    message, data_len);
    if (sent == -1)
    {
        fprintf(stderr, "Failed to send datagram\n");
    }

    close(sockfd);
    return 0;
}
    

udp_receiver.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <sys/ioctl.h>

#define BUFFER_SIZE 4096  // 接收缓冲区大小

// 校验和计算函数(与发送端一致)
unsigned short checksum(unsigned short *ptr, int nbytes)
{
    register long sum = 0;
    unsigned short oddbyte;
    register short answer = 0;

    while (nbytes > 1)
    {
        sum += *ptr++;
        nbytes -= 2;
    }

    if (nbytes == 1)
    {
        oddbyte = 0;
        *((uint8_t *)&oddbyte) = *(uint8_t *)ptr;
        sum += oddbyte;
    }

    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    answer = (short)~sum;

    return answer;
}

// 开启混杂模式(可选,用于捕获非本机的数据包)
int enable_promiscuous_mode(int sockfd, const char *if_name)
{
    struct ifreq ifr;
    strncpy(ifr.ifr_name, if_name, IFNAMSIZ - 1);

    // 获取当前接口标志
    if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) == -1)
    {
        perror("ioctl get flags failed");
        return -1;
    }

    // 开启混杂模式
    ifr.ifr_flags |= IFF_PROMISC;
    if (ioctl(sockfd, SIOCSIFFLAGS, &ifr) == -1)
    {
        perror("ioctl set promiscuous failed");
        return -1;
    }

    printf("Enabled promiscuous mode on interface: %s\n", if_name);
    return 0;
}

// 解析并打印收到的 UDP 数据包
void parse_udp_packet(uint8_t *buffer, ssize_t len)
{
    if (len < sizeof(struct iphdr) + sizeof(struct udphdr))
    {
        printf("Packet too small\n");
        return;
    }

    // 解析 IP 头部
    struct iphdr *ip = (struct iphdr *)buffer;
    if (ip->version != 4)
    {
        printf("Not an IPv4 packet\n");
        return;
    }

    // 验证 IP 校验和
    unsigned short ip_check = ip->check;
    ip->check = 0;  // 临时清零
    if (checksum((unsigned short *)ip, ip->ihl * 4) != ip_check)
    {
        printf("IP checksum verification failed\n");
        ip->check = ip_check;  // 恢复原值
        return;
    }
    ip->check = ip_check;  // 恢复原值

    // 只处理 UDP 协议
    if (ip->protocol != IPPROTO_UDP)
    {
        printf("Not a UDP packet\n");
        return;
    }

    // 解析 UDP 头部
    int ip_header_len = ip->ihl * 4;
    struct udphdr *udp = (struct udphdr *)(buffer + ip_header_len);

    // 计算 UDP 数据长度
    uint16_t udp_len = ntohs(udp->len);
    uint16_t data_len = udp_len - sizeof(struct udphdr);
    uint8_t *data = buffer + ip_header_len + sizeof(struct udphdr);

    // 打印基本信息
    printf("\nReceived UDP packet:\n");
    printf("  Source IP: %s\n", inet_ntoa(*(struct in_addr *)&ip->saddr));
    printf("  Dest IP: %s\n", inet_ntoa(*(struct in_addr *)&ip->daddr));
    printf("  Source Port: %d\n", ntohs(udp->source));
    printf("  Dest Port: %d\n", ntohs(udp->dest));
    printf("  UDP Length: %d\n", udp_len);
    printf("  Data Length: %d\n", data_len);
    if (data_len > 0)
    {
        printf("  Data: %.*s\n", (int)data_len, data);  // 打印字符串形式的数据
    }
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        fprintf(stderr, "Usage: %s <interface> <port>\n", argv[0]);
        fprintf(stderr, "Example: %s eth0 8080\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    const char *if_name = argv[1];
    int port = atoi(argv[2]);

    // 创建原始套接字(捕获所有 IP 数据包)
    int sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP));
    if (sockfd == -1)
    {
        perror("socket creation failed");
        fprintf(stderr, "需要 root 权限运行\n");
        exit(EXIT_FAILURE);
    }

    // 开启混杂模式(可选)
    if (enable_promiscuous_mode(sockfd, if_name) == -1)
    {
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    printf("Listening for UDP packets on port %d...\n", port);
    uint8_t buffer[BUFFER_SIZE];
    struct sockaddr_in src_addr;
    socklen_t addr_len = sizeof(src_addr);

    while (1)
    {
        // 接收数据包
        ssize_t len = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, 
                              (struct sockaddr *)&src_addr, &addr_len);
        if (len == -1)
        {
            perror("recvfrom failed");
            continue;
        }

        // 解析并打印 UDP 数据包
        parse_udp_packet(buffer, len);
    }

    close(sockfd);
    return 0;
}
    

编译运行:

# 编译发送端
gcc udp_sender.c main_sender.c -o udp_sender

# 编译接收端
gcc udp_receiver.c -o udp_receiver

# 运行接收端(需要 root 权限,指定网络接口和监听端口)
sudo ./udp_receiver eth0 8080

# 运行发送端(需要 root 权限,指定源/目标信息和发送内容)
sudo ./udp_sender 192.168.1.100 192.168.1.200 1234 8080 "Hello from raw UDP"

https://github.com/0voice


网站公告

今日签到

点亮在社区的每一天
去签到