Linux中利用RAW SOCKET直接通过网卡收发数据

问题背景:公司原来为了搜索局域网内的网络视频解码器开发了一个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的应用程序。

====

http://blog.sina.com.cn/s/blog_5eb41f5a0100f9ch.html

你可能感兴趣的:(linux,socket,null,嵌入式,byte,linux内核)