Linux中利用RAW SOCKET直接通过网卡收发数据
来源: http://blog.sina.com.cn/s/blog_5eb41f5a0100f9ch.html
问题背景:公司原来为了搜索局域网内的网络视频解码器开发了一个Decoder Finder,用的是UDP广播的方式。现在韩国的客户发现当IP地址和PC不在同一网段时,无法搜索到decoder,人家还找了一个他们的软件,暴强,就算是IP地址全是0,照搜不误。
问题分析:
PC端,其实也就是用winpcap,直接和网卡通信,把消息包发出来,并且在接受响应。这样数据包不经过IP和UDP协议栈,IP地址有效无效都无所谓了。发送消息的时候,把目的MAC填为全F,做成广播包。
嵌入式端,操作系统式uCLinux,使用AF_PACKET协议簇,RAW_SOCKET类型的端口即可和网卡驱动直接通信,绕过IP以上的协议栈。基础知识可参考Linux下sniffer程序的实现和Linux下制定网卡收发包。
以太网的侦结构如下:
-----------------------------------------------------
| 目的地址
-----------------------------------------------------
|
问题解决:
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)
{
}
char
//不绑定网卡,不绑定地址,来了的数据包都接收
i32Len = recvfrom(SockFd, szBuff, sizeof(szBuff), 0, NULL, NULL);
if ( i32Len < 14 )
{
}
接受的部分就算完了。收到的数据是未经协议栈处理的,目的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
stTagAddr.sll_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)
{
}
stTagAddr.sll_ifindex
stTagAddr.sll_pkttype
stTagAddr.sll_halen
//填写目标MAC地址
stTagAddr.sll_addr[0]
stTagAddr.sll_addr[1]
stTagAddr.sll_addr[2]
stTagAddr.sll_addr[3]
stTagAddr.sll_addr[4]
stTagAddr.sll_addr[5]
//填充帧头和内容
i32Len = sendto(SockFd, (INT8 *)szbuff, sizeof(szbuff), 0, (const struct sockaddr *)&stTagAddr, sizeof(stTagAddr));
可以用ethereal抓包看看你发出来的数据是否正确。另外,如果你的数据不足46字节(不含帧头)Linux的网卡驱动程序会把数据补0凑足46个字节,接收端处理时应当注意。
发送过程小结:应用程序(选定用来发送数据包的网卡)----》内核-----》网卡驱动程序-----》网卡---》PC的网卡----》winpcap-----》PC的应用程序。