问题背景:公司原来为了搜索局域网内的网络视频解码器开发了一个Decoder Finder,用的是UDP广播的方式。现在韩国的客户发现当IP地址和PC不在同一网段时,无法搜索到decoder,人家还找了一个他们的软件,暴强,就算是IP地址全是0,照搜不误。
问题分析:
PC端,其实也就是用winpcap,直接和网卡通信,把消息包发出来,并且在接受响应。这样数据包不经过IP和UDP协议栈,IP地址有效无效都无所谓了。发送消息的时候,把目的MAC填为全F,做成广播包。
嵌入式端,操作系统式uCLinux,使用AF_PACKET协议簇,RAW_SOCKET类型的端口即可和网卡驱动直接通信,绕过IP以上的协议栈。基础知识可参考Linux下sniffer程序的实现和Linux下制定网卡收发包。
以太网的侦结构如下:
-----------------------------------------------------
| 目的地址 | 源地址 | 类型 | 数据 |
-----------------------------------------------------
| 6 byte | 6 byte | 2 byte | 46~1500 byte |
问题解决:
1.Linux下直接从网卡接收数据
非常简单的。当然,前提条件是Linux内核配置中,已经包含了PACKET SOCKET的支持。
要记得,我们后面面对的数据,就是以太网一级的。
INT32 SockFd;
//第一个参数,协议簇,填写PF_PACKET
//第二个参数,填写SOCK_RAW,表明这是原始socket,这样数据包就不会经过协议栈的处理了。
//第三个参数,希望接受到的消息类型。参考If_ether.h中的协议定义。这里实际上也可以自己定义。
//它表明了上层协议的类型(注意,我们现在是在直接和网卡打交道)。
//内核会去判断消息头里面的类型,如果匹配,就往应用层发,如果不匹配,就不发。
//ETH_P_ALL表示不管什么类型的协议都往应用层发。
SockFd = socket(PF_PACKET, SOCK_RAW, htons(VSTRONG_PROTOCOL));
if (-1 == SockFd)
{
DEBUGMSG(1,("Level:%d [Searcher.main] create socket error.\n", ERR));
return 1;
}
char szBuff[2048] = {0};
//不绑定网卡,不绑定地址,来了的数据包都接收
i32Len = recvfrom(SockFd, szBuff, sizeof(szBuff), 0, NULL, NULL);
if ( i32Len < 14 )
{
//接收的数据还不到一个帧头
DEBUGMSG(1,("Level:%d [Searcher.main]recvfrom returns %d \n", ERR,i32Len));
return 1;
}
接受的部分就算完了。收到的数据是未经协议栈处理的,目的MAC,原始MAC历历在目。
你可能会说,不对啊,网卡看到目的MAC和自己的MAC不匹配,就不会接收消数据啊。
的确是这样的,如果要收到不是发给自己的数据包,还得将网卡设置为混杂模式。但是
也有例外,一是目的MAC地址为全F的情况,视为广播地址,网卡看到全F的地址会处理的;
另外就是组播的情况。如上所述,我们是在PC端发搜索消息时,用了广播地址。
小结一下:数据的流通过程就是:
PC发消息(应用层)--》PC的网卡-----》Decoder的网卡(判断是广播地址)----》驱动程序-----》内核(判断是ROW SOCKET,跳过协议栈的处理)----》应用程序
=============================================================================================
2.Linux下直接从网卡发送数据
发送的时候,也是要用户自己构造数据帧,另外还需要填写一个地址数据。
#include <linux/if_packet.h>
#include <linux/if.h>
#include <sys/ioctl.h>
struct sockaddr_ll stTagAddr;
memset(&stTagAddr, 0 , sizeof(stTagAddr));
stTagAddr.sll_family = AF_PACKET;//填写AF_PACKET,不再经协议层处理
stTagAddr.sll_protocol = htons(VSTRONG_PROTOCOL);
int ret;
struct ifreq req;
int sd;
sd = socket(PF_INET,SOCK_DGRAM,0);//这个sd就是用来获取eth0的index,完了就关闭
strncpy(req.ifr_name,"eth0",4);//通过设备名称获取index
ret=ioctl(sd,SIOCGIFINDEX,&req);
close(sd);
if (ret==-1)
{
DEBUGMSG(1,("Level:%d [Searcher main]Get eth0 index err \n", ERR));
}
stTagAddr.sll_ifindex = req.ifr_ifindex;//网卡eth0的index,非常重要,系统把数据往哪张网卡上发,就靠这个标识
stTagAddr.sll_pkttype = PACKET_OUTGOING;//标识包的类型为发出去的包
stTagAddr.sll_halen = 6; //目标MAC地址长度为6
//填写目标MAC地址
stTagAddr.sll_addr[0] = 0x00;
stTagAddr.sll_addr[1] = 0x01;
stTagAddr.sll_addr[2] = 0x02;
stTagAddr.sll_addr[3] = 0x03;
stTagAddr.sll_addr[4] = 0x04;
stTagAddr.sll_addr[5] = 0x05;
//填充帧头和内容
i32Len = sendto(SockFd, (INT8 *)szbuff, sizeof(szbuff), 0, (const struct sockaddr *)&stTagAddr, sizeof(stTagAddr));
可以用ethereal抓包看看你发出来的数据是否正确。另外,如果你的数据不足46字节(不含帧头)Linux的网卡驱动程序会把数据补0凑足46个字节,接收端处理时应当注意。
发送过程小结:应用程序(选定用来发送数据包的网卡)----》内核-----》网卡驱动程序-----》网卡---》PC的网卡----》winpcap-----》PC的应用程序。