原始套接字(Raw Socket)提供了一种机制,允许应用程序直接访问底层传输协议,绕过操作系统提供的传输层接口。这种套接字通常用于实现新的协议或对现有协议进行低级别的操作。
以下是对原始套接字的详细介绍:
定义与用途:
创建:
socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);特权:
工作方式:
用途与限制:
注意事项:
跨平台的差异:
总的来说,原始套接字是一个非常强大的工具,但也需要谨慎使用。正确使用它需要对网络协议有深入的理解,而滥用它可能导致网络问题或被视为恶意活动。
创建链路层的原始套接字允许我们直接与链路层设备(例如以太网适配器)交互,从而可以发送和接收链路层帧,例如以太网帧。这在某些网络工具和应用中非常有用,例如包捕获工具、桥接和交换应用程序。
以下是如何在Linux中创建链路层的原始套接字:
包括必要的头文件:
#include
#include
#include
#include
#include /* 需要以太网协议宏 */
创建套接字:
使用socket()系统调用创建一个原始套接字,具体地,使用AF_PACKET作为地址族和SOCK_RAW作为套接字类型。
int sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sockfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
注意:htons(ETH_P_ALL)表示该套接字将接收所有类型的以太网帧。
绑定到具体的网络接口:
如果想指定从哪个接口接收帧,可以使用bind()函数。首先,需要查找网络接口的索引号。
char *interface_name = "eth0"; // 例如
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(interface_name);
if (bind(sockfd, (struct sockaddr*)&sa, sizeof(struct sockaddr_ll)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
发送和接收帧:
一旦套接字被创建(并可能被绑定),可以使用标准的sendto()和recvfrom()函数发送和接收数据。接收到的数据将包括完整的链路层帧。
关闭套接字:
使用close(sockfd);关闭套接字。
需要注意的是,访问链路层通常需要特权,因此上述代码通常需要以root权限运行。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFFER_SIZE 2048
int main() {
int sockfd, n;
char buffer[BUFFER_SIZE];
struct sockaddr_ll sa;
struct ifreq ifr; // For setting promiscuous mode
char *interface_name = "eth0";
sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sockfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// Enable promiscuous mode
strncpy(ifr.ifr_name, interface_name, 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);
}
memset(&sa, 0, sizeof(struct sockaddr_ll));
sa.sll_family = AF_PACKET;
sa.sll_protocol = htons(ETH_P_ALL);
sa.sll_ifindex = if_nametoindex(interface_name);
if (bind(sockfd, (struct sockaddr*)&sa, sizeof(struct sockaddr_ll)) == -1) {
perror("bind");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("Listening on %s in promiscuous mode...\n", interface_name);
while (1) {
n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, NULL, NULL);
if (n == -1) {
perror("recvfrom");
break;
}
printf("Received a frame of length: %d bytes\n", n);
}
close(sockfd);
return 0;
}
这个程序的主要目的是使用原始套接字在Linux环境中捕获网络接口上的数据帧。这种原始套接字的功能允许应用程序直接处理网络层以下的协议,如链路层。下面是对程序的详细分析:
创建原始套接字:
socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))创建一个原始套接字,这允许程序接收所有类型的数据帧。启用混杂模式:
ioctl系统调用与SIOCGIFFLAGS命令获取当前接口的标志。IFF_PROMISC标志,以启用混杂模式。ioctl系统调用与SIOCSIFFLAGS命令来更新接口的标志。绑定套接字到特定接口:
eth0)接收数据帧,程序绑定了原始套接字到这个接口。if_nametoindex函数获取接口的索引。bind函数绑定套接字。捕获循环:
recvfrom函数来接收数据帧。资源清理:
close函数关闭原始套接字以释放资源。总体来说,这个程序创建并绑定了一个原始套接字,启用了混杂模式,并持续监听指定网络接口上的所有数据帧。这种捕获能力使它非常适合于网络监控和分析任务。
启用混杂模式和绑定到特定接口是两个相互独立但在某些用例中都必需的操作。
启用混杂模式 (Promiscuous Mode):
绑定到特定接口:
eth0)接收数据帧。没有这个绑定,套接字可能会从任何启用了混杂模式的接口接收数据帧。总结来说,启用混杂模式是为了能够看到所有的流量,而绑定到特定接口是为了确定从哪个接口接收这些流量。两者结合使用,允许我们专门监视特定接口上的所有流量。
struct sockaddr_ll 是Linux特有的数据结构,用于定义在数据链路层(Layer 2)上的套接字地址,特别是当使用PF_PACKET协议族时。这个结构提供了原始套接字用来发送和接收数据包所需的所有信息。我们来详细分析它的各个字段:
sll_family: 这是地址族字段。对于这个特定的结构,它总是设置为AF_PACKET。
sll_protocol: 这是以网络字节序表示的物理层协议。例如,要接收所有的以太网帧,可以设置为htons(ETH_P_ALL)。
sll_ifindex: 这是接口索引,用于标识要绑定的网络接口。通常,可以使用if_nametoindex函数将接口名(如 “eth0”)转换为其索引。
sll_hatype: 这是ARP硬件类型。例如,以太网的硬件类型是ARPHRD_ETHER。
sll_pkttype: 描述了数据包的类型,可以是如下类型之一:
sll_halen: 物理层地址的长度。例如,对于以太网,这个长度是6。
sll_addr: 存储物理层地址(通常是MAC地址)的数组。由于MAC地址的长度为6字节而这个字段为8字节,所以通常后两个字节保持为0。
总之,struct sockaddr_ll结构提供了在数据链路层上发送和接收数据包所需的所有信息,特别是当使用原始套接字和PF_PACKET协议族时。
PF_PACKET 和 AF_PACKET 实际上是同一个值,只是在不同的上下文中使用了不同的名称。在套接字编程中,通常使用 AF_ 前缀来表示地址族,而使用 PF_ 前缀来表示协议族。在 Linux 中,PF_PACKET 和 AF_PACKET 的值是相同的,因此在 socket() 调用中使用哪个取决于个人习惯。