使用Raw Socket构建客户端服务端程序遇权限问题及技术咨询
关于Raw Socket客户端-服务端实现的问题解答
别担心,第一次接触raw socket踩这些坑太正常了,我来一步步帮你拆解问题并修正代码。
先看你提供的代码和遇到的错误:
你的代码
服务端代码(server.c)
/* server.c */ #include "../cn.h" #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> int main(int argc, char const *argv[]) { int sfd = socket(AF_INET, SOCK_RAW, 253); if (sfd < 0) { perror("Could not create socket"); exit(0); } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); if (bind(sfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("Could not bind"); } int nsfd = accept(sfd, NULL, NULL); if (nsfd < 0) { perror("Could not accept"); exit(0); } char buffer[20]; int sz; while (1) { if ((sz = read(nsfd, buffer, 20)) < 0) { perror("Could not read"); } else { buffer[sz] = '\0'; strcat(buffer, " form server"); if (write(nsfd, buffer, strlen(buffer)) < 0) { perror("Could not send"); } } } return 0; }
客户端代码(client.c)
#include "../cn.h" #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> int main(int argc, char const *argv[]) { int sfd = socket(AF_INET, SOCK_RAW, 253); if (sfd < 0) { perror("Could not create socket"); exit(0); } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); if (connect(sfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("Could not connect"); exit(0); } char buffer[20]; int sz; while (1) { scanf("%[^\n]s", buffer); while (getchar() != '\n') ; if (write(sfd, buffer, strlen(buffer)) < 0) { perror("Could not write"); } else if ((sz = read(sfd,buffer, 20)) < 0) { perror("Could not read"); } else { buffer[sz] = '\0'; printf("Reading: %s\n", buffer); } } return 0; }
你的问题解答
1. 遗漏的配置/步骤
你主要遗漏了两个关键环节:
- 需要root权限运行程序:raw socket属于特权资源,普通用户无法创建,必须用
sudo启动你的服务端和客户端程序。 - 错误使用了面向连接的API:
accept()、connect()、read()/write()这些函数是为TCP这类面向连接的协议设计的,raw socket是无连接的,不能用这些函数,需要换成recvfrom()、sendto()来收发数据包。
2. 为何创建raw socket不被允许?
因为raw socket允许程序直接操作网络层(IP)甚至链路层的数据包,能够绕过内核的传输层协议栈(比如TCP/UDP的校验、重传等逻辑),属于高权限操作。为了系统安全,Linux等Unix-like系统限制只有root用户(或拥有CAP_NET_RAW能力的用户)才能创建raw socket。
3. 如何使用带有自定义协议ID的raw socket?
自定义协议ID(你用的253是IANA预留的私有协议号,合法可用)的raw socket使用步骤如下:
核心要点:
- 内核不会帮你处理自定义协议的传输逻辑,所有数据包的构造、解析都需要自己完成。
- 用
recvfrom()接收数据包并获取发送方地址,用sendto()发送数据包到目标地址。 - 可选设置
IP_HDRINCL选项,手动构造IP头(默认内核会帮你添加IP头)。
修正后的示例代码:
服务端(修正版)
/* server.c */ #include "../cn.h" #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> int main(int argc, char const *argv[]) { // 创建自定义协议253的raw socket int sfd = socket(AF_INET, SOCK_RAW, 253); if (sfd < 0) { perror("Could not create socket"); exit(EXIT_FAILURE); } struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // 绑定到回环地址 if (bind(sfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("Could not bind"); close(sfd); exit(EXIT_FAILURE); } char buffer[1024]; struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int sz; while (1) { // 接收客户端数据包 sz = recvfrom(sfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&client_addr, &client_len); if (sz < 0) { perror("Could not recvfrom"); continue; } buffer[sz] = '\0'; printf("Received from client: %s\n", buffer); // 构造回复内容 strcat(buffer, " from server"); // 发送回复到客户端 sz = sendto(sfd, buffer, strlen(buffer), 0, (struct sockaddr*)&client_addr, client_len); if (sz < 0) { perror("Could not sendto"); } } close(sfd); return 0; }
客户端(修正版)
/* client.c */ #include "../cn.h" #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> int main(int argc, char const *argv[]) { int sfd = socket(AF_INET, SOCK_RAW, 253); if (sfd < 0) { perror("Could not create socket"); exit(EXIT_FAILURE); } struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); char buffer[1024]; struct sockaddr_in recv_addr; socklen_t recv_len = sizeof(recv_addr); int sz; while (1) { printf("Enter message: "); // 读取输入(修正原scanf的问题,避免缓冲区溢出) fgets(buffer, sizeof(buffer)-1, stdin); // 去掉换行符 buffer[strcspn(buffer, "\n")] = '\0'; // 发送到服务端 sz = sendto(sfd, buffer, strlen(buffer), 0, (struct sockaddr*)&server_addr, sizeof(server_addr)); if (sz < 0) { perror("Could not sendto"); continue; } // 接收服务端回复 sz = recvfrom(sfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&recv_addr, &recv_len); if (sz < 0) { perror("Could not recvfrom"); continue; } buffer[sz] = '\0'; printf("Reading: %s\n", buffer); } close(sfd); return 0; }
运行方式:
# 启动服务端 sudo ./server # 启动客户端(另一个终端) sudo ./client
4. 优质学习资料
- Man手册:直接查
man 7 raw和man 2 socket,这是最权威的官方文档,涵盖了raw socket的权限、选项、使用场景等所有细节。 - 《UNIX网络编程 卷1:套接字联网API》:经典的网络编程书籍,有专门章节讲解raw socket的实现,包括自定义协议、数据包构造等内容。
- Linux内核文档:查看内核源码中的
Documentation/networking/raw.txt,里面详细说明了Linux下raw socket的底层实现和特殊注意事项。
内容的提问来源于stack exchange,提问作者Ricky




