• 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下制定网卡收发包

     

    以太网的侦结构如下:

    -----------------------------------------------------
    | 目的地址   | 源地址   | 类型     | 数据           |
    -----------------------------------------------------
      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的应用程序。


  • 相关阅读:
    冒泡排序
    选择排序
    JavaScript学习笔记---数组对象
    数字时钟
    操作字符串
    当前时间
    倒计时 定时器
    滚动文字
    查找替换文字
    JavaScript学习笔记---对象 时间对象 字符串对象
  • 原文地址:https://www.cnblogs.com/fengty90/p/3768877.html
Copyright © 2020-2023  润新知