我试图在同一台机器上使用我自己的自定义协议ID在PF_SOCKET
上发送和接收types为SOCK_RAW
数据包。 这是我的发件人和收件人示例代码 –
sender.c
#include<sys/socket.h> #include<linux/if_packet.h> #include<linux/if_ether.h> #include<linux/if_arp.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #define CUSTOM_PROTO 0xB588 int main () { int sockfd = -1; struct sockaddr_ll dest_addr = {0}, src_addr={0}; char *buffer = NULL; struct ethhdr *eh; sockfd = socket(PF_PACKET, SOCK_RAW, htons(CUSTOM_PROTO) ); if ( sockfd == -1 ) { perror("socket"); return -1; } buffer = malloc(1518); eh = (struct ethhdr *)buffer; dest_addr.sll_ifindex = if_nametoindex("eth0"); dest_addr.sll_addr[0] = 0x0; dest_addr.sll_addr[1] = 0xc; dest_addr.sll_addr[2] = 0x29; dest_addr.sll_addr[3] = 0x49; dest_addr.sll_addr[4] = 0x3f; dest_addr.sll_addr[5] = 0x5b; dest_addr.sll_addr[6] = 0x0; dest_addr.sll_addr[7] = 0x0; //other host MAC address unsigned char dest_mac[6] = {0x0, 0xc, 0x29, 0x49, 0x3f, 0x5b}; /*set the frame header*/ memcpy((void*)buffer, (void*)dest_mac, ETH_ALEN); memcpy((void*)(buffer+ETH_ALEN), (void*)dest_mac, ETH_ALEN); eh->h_proto = htons(PAVAN_PROTO); memcpy((void*)(buffer+ETH_ALEN+ETH_ALEN + 2), "Pavan", 6 ); int send = sendto(sockfd, buffer, 1514, 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr) ); if ( send == -1 ) { perror("sendto"); return -1; } return 0; }
receiver.c
#include<sys/socket.h> #include<linux/if_packet.h> #include<linux/if_ether.h> #include<linux/if_arp.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #define CUSTOM_PROTO 0xB588 int main () { int sockfd = -1; struct sockaddr_ll dest_addr = {0}, src_addr={0}; char *recvbuf = malloc(1514); sockfd = socket(PF_PACKET, SOCK_RAW, htons(CUSTOM_PROTO) ); if ( sockfd == -1 ) { perror("socket"); return -1; } int len = recvfrom(sockfd, recvbuf, 1514, 0, NULL, NULL); printf("I received: \n"); return 0; }
发件人和收件人都在Ubuntu Virtualbox上运行。 问题是接收机挂在recvfrom
。 但是在receiver.c
,如果我把htons(CUSTOM_PROTO)
htons(ETH_P_ALL)
,接收器工作的很好。
为什么内核没有将自定义协议ID的数据包传递给我的自定义协议ID套接字?
我在GDB中validation过,当我用htons(ETH_P_ALL)
接收数据包htons(ETH_P_ALL)
更新:如果我select本地回环lo
和MAC地址00:00:00:00:00:00
,而不是eth0
接口和它的相应MAC地址, CUSTOM_PROTO
就可以正常工作!
更新2如果发件人和收件人在不同的机器上, CUSTOM_PROTO
可以正常工作。 这个发现和prev更新让我怀疑在eth0
上发送的数据包没有被同一台机器接收到。 但是ETH_P_ALL
在同一台机器上工作的事实驳斥了我的怀疑。
协议ETH_P_ALL
具有捕获传出数据包的特殊作用。
使用任何不等于ETH_P_ALL
协议的接收器套接字接收来自设备驱动程序的协议数据包。
带有协议ETH_P_ALL
套接字在将传出数据包发送到设备驱动程序和从设备驱动程序接收的所有传入数据包之前接收所有数据包。
发送到回环设备的数据包从该设备出去,然后从设备接收到相同的数据包作为传入。 因此,当CUSTOM_PROTO
与回送一起使用时,套接字将捕获具有自定义协议的数据包作为传入。
请注意,如果ETH_P_ALL
与回送设备一起使用,则每个数据包都会被接收两次。 一旦被捕获为传出,并且第二次被传入。
在eth0
的情况下,数据包从设备传输。 所以,这样的数据包到设备驱动程序,然后可以在物理以太网端口的另一侧看到它们。 例如,使用VirtualBox“Host-only”网络适配器,这些数据包可以被主机系统中的一些嗅探器捕获。
但是,传输到物理端口(或其仿真)的数据包不会重定向到该端口。 所以,他们没有收到来自设备的传入。 这就是为什么这样的数据包只能通过ETH_P_ALL
在传出方向捕获,而在传入方向上不能被CUSTOM_PROTO
ETH_P_ALL
。
从技术上讲,应该可以准备特殊设置来做外部数据包回送(来自设备端口的数据包应该被发送回该端口)。 在这种情况下,行为应该类似于回送设备。
查看内核文件net/core/dev.c
有两个不同的列表:
struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly; struct list_head ptype_all __read_mostly; /* Taps */
列表ptype_all
用于协议ETH_P_ALL
套接口处理程序。 列表ptype_base
用于具有正常协议的处理程序。
从dev_hard_start_xmit()
调用的dev_hard_start_xmit()
有一个传出包的挂钩:
if (!list_empty(&ptype_all)) dev_queue_xmit_nit(skb, dev);
对于传出数据包, dev_queue_xmit_nit()
函数dev_queue_xmit_nit()
来处理每个ETH_P_ALL
每个项目的ptype_all
。 最后,带有协议ETH_P_ALL
的类型为AF_SOCKET
的套接字捕获该输出分组。
所以,观察到的行为与任何自定义协议都没有关系。 ETH_P_IP
可以观察到相同的行为。 在这种情况下,接收方能够捕获所有传入的IP数据包,但是它不能捕获sender.c
从"eth0"
发送到"eth0"
设备的MAC地址的IP数据包。
它也可以被tcpdump
看到。 如果使用只捕获传入数据包的选项调用tcpdump
(不同版本的tcpdump
使用不同的命令行参数来启用此类过滤),则不捕获发送方发送的数据包。
在同一台机器上需要通过协议ID区分数据包的初始任务可以使用ETH_P_ALL
来解决。 接收机应该捕获所有数据包并检查协议,例如:
while (1) { int len = recvfrom(sockfd, recvbuf, 1514, 0, NULL, NULL); if (ntohs(*(uint16_t*)(recvbuf + ETH_ALEN + ETH_ALEN)) == CUSTOM_PROTO) { printf("I received: \n"); break; } }
有用的参考“kernel_flow”与一个很好的图http://img.zgserver.com/c/Network_data_flow_through_kernel.png
它基于2.6.20内核,但在现代内核中, ETH_P_ALL
以相同的方式处理。