clinux原始套接字

一、基本概念
原始套接字(Raw Socket)是一种特殊的网络套接字,它允许应用程序直接访问底层传输协议,绕过操作系统提供的传输层接口,这种套接字通常用于实现新的协议或对现有协议进行低级别的操作,如自定义IP包的构造和发送,在Linux中,原始套接字广泛应用于网络诊断工具(如ping、traceroute)、网络攻击与防御以及某些类型的网络测试。
二、创建方法
创建原始套接字的过程与创建其他类型的套接字相似,但需要指定特定的协议族(如AF_INET表示IPv4)和套接字类型(SOCK_RAW表示原始套接字),以下是一个创建用于IPv4和ICMP的原始套接字的示例:
int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sockfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}三、权限需求
由于原始套接字允许直接访问底层协议,并可能被用于伪造数据包,因此它通常需要特殊权限(如root权限)才能创建和使用。
四、工作方式
发送数据
当使用原始套接字发送数据时,应用程序需要负责构建完整的传输层头部(如TCP、UDP或ICMP头部),这给了我们控制头部字段的能力,例如伪造源IP地址,以下是一个发送自定义ICMP回显请求的示例:
struct icmphdr hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.type = ICMP_ECHO;
hdr.code = 0;
hdr.checksum = 0;
hdr.un.echo.id = getpid();
for (int i = 0; i < sizeof(hdr)/2; i++) {
hdr.checksum += ((u_short *)&hdr)[i];
}
hdr.checksum += (hdr.checksum >> 16);
sendto(sockfd, &hdr, sizeof(hdr), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));接收数据
当使用原始套接字接收数据时,会得到底层协议的完整头部,应用程序需要解析这些头部以获取所需的信息,以下是一个接收ICMP回显应答的示例:
char buffer[BUFFER_SIZE];
recvfrom(sockfd, buffer, BUFFER_SIZE, 0, NULL, NULL);
struct icmphdr *recv_icmp = (struct icmphdr *)buffer;
if (recv_icmp->type == ICMP_ECHOREPLY) {
printf("Received ICMP echo reply
");
}五、用途与限制
用途
网络诊断工具:如ping、traceroute等,用于测试网络连通性和诊断网络问题。

网络攻击与防御:用于实现自定义的网络攻击或防御策略。
网络测试:用于测试网络协议的正确性和性能。
限制
操作系统处理:大多数操作系统默认会处理某些协议(如ICMP回显请求和回显应答),这可能会导致原始套接字无法接收到这些协议的数据包。
跨平台差异:不同的操作系统在实现和行为上可能存在细微差别,这需要在编写跨平台代码时特别注意。
安全风险:由于原始套接字的强大功能,滥用可能导致网络安全问题或被视为恶意活动。
六、链路层原始套接字
链路层原始套接字允许直接与链路层设备(如以太网适配器)交互,发送和接收链路层帧(如以太网帧),这对于需要直接处理链路层数据的应用非常有用,如包捕获工具、桥接和交换应用程序。
创建一个链路层原始套接字的示例如下:
int sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sockfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}绑定到特定网络接口并启用混杂模式的示例如下:

struct sockaddr_ll sa;
memset(&sa, 0, sizeof(struct sockaddr_ll));
sa.sll_family = AF_PACKET;
sa.sll_protocol = htons(ETH_P_ALL);
sa.sll_ifindex = if_nametoindex("eth0");
if (bind(sockfd, (struct sockaddr *)&sa, sizeof(struct sockaddr_ll)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
// Enable promiscuous mode
strncpy(ifr.ifr_name, "eth0", IFNAMSIZ);
if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) == -1) {
perror("ioctl SIOCGIFFLAGS");
close(sockfd);
exit(EXIT_FAILURE);
}
ifr.ifr_flags |= IFF_PROMISC;
if (ioctl(sockfd, SIOCSIFFLAGS, &ifr) == -1) {
perror("ioctl SIOCSIFFLAGS");
close(sockfd);
exit(EXIT_FAILURE);
}发送和接收链路层帧的示例如下:
// Send a frame
char frame[] = { /* Ethernet frame data */ };
sendto(sockfd, frame, sizeof(frame), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
// Receive a frame
char buffer[BUFFER_SIZE];
recvfrom(sockfd, buffer, BUFFER_SIZE, 0, NULL, NULL);七、注意事项与常见问题解答
问题1:如何确保原始套接字发送的数据包格式正确?
答:由于原始套接字需要手动构建数据包,因此必须仔细按照协议规范构建数据包,并计算校验和等必要字段,建议参考相关RFC文档或使用现有的库函数来辅助构建。
问题2:为什么原始套接字无法接收某些协议的数据包?
答:这是因为操作系统可能会对某些协议(如ICMP回显请求和回显应答)进行特殊处理,导致这些数据包不会被传递给原始套接字,可以通过设置套接字选项或使用其他机制来绕过这种限制。
问题3:如何在多网卡系统中指定原始套接字使用的网络接口?
答:可以使用bind函数将原始套接字绑定到指定的网络接口,在绑定之前,需要查找网络接口的索引号,并将其设置为sockaddr_ll结构体的sll_ifindex字段。
小伙伴们,上文介绍了“clinux原始套接字”的内容,你了解清楚吗?希望对你有所帮助,任何问题可以给我留言,让我们下期再见吧。
文章来源网络,作者:运维,如若转载,请注明出处:https://shuyeidc.com/wp/53003.html<
