【问题标题】:How to send data over a raw ethernet socket using sendto without using sockaddr_ll?如何在不使用 sockaddr_ll 的情况下使用 sendto 通过原始以太网套接字发送数据?
【发布时间】:2014-02-20 02:54:08
【问题描述】:

我正在开展一个使用 Enea OSE 的项目,它是一个实时嵌入式操作系统。我想你们中的许多人都不熟悉这个操作系统,但我认为无论如何你都可以回答我的问题。我想使用接口“eth1”在 2 个卡之间发送一个原始以太网帧,这些接口通过以太网电缆(没有开关或任何东西)直接连接到另一个。 “eth1”接口没有分配任何IP地址。

我可以简单地使用

声明一个套接字
int s = socket(AF_INET, RAW_SOCKET, ...)

然后我可以使用以下方法将套接字绑定到适当的设备接口:

setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, "eth1", 4);

为了将数据从一张卡发送到另一张卡,我的计划是手动用源和目标 MAC 地址填充以太网缓冲区阵列。然后使用 sendto(....) 发送数据

但是 sendto 例程还需要一个带有信息的 sockaddr 结构:

  sendto(int sockfd, const void *buf, size_t len, int flags,
           const struct sockaddr *dest_addr, socklen_t addrlen);

在我在互联网上找到的示例中,当 IP 地址已知时使用 sockaddr_in,而对于原始帧使用 sockaddr_ll。但是 OSE 不提供 sockaddr_ll 结构。但是它提供了 sockaddr ,我不知道我是否可以将其用于原始以太网帧?

struct sockaddr {
    unsigned short    sa_family;    // address family, AF_xxx
    char              sa_data[14];  // 14 bytes of protocol address
};

我也尝试使用NULL,sockaddr字段在调用sendto()时,发送失败。这让我想到了我的问题。我可以使用简单的 sockaddr 结构来填写目标 MAC 地址而不使用任何 IP 吗?那看起来怎么样? (我还没有看到任何显示这一点的代码)。为什么我们甚至必须提供一个 sockaddr 结构?以太网数据缓冲区不是已经包含这些信息了吗?最后,在不为网络设备分配 IP 地址的情况下,我想要实现的目标是否可行?

我知道我正在尝试做的事情看起来很奇怪,但这是有原因的 :) 如果您能告诉我如何在不使用 sockaddr_ll的情况下完成此操作,我将非常高兴> 结构。

【问题讨论】:

    标签: c sockets ose


    【解决方案1】:

    很抱歉之前的含糊回答。 这是我的完整答案。希望对您有所帮助。

    回答您的问题:是的,您可以只使用 MAC 地址发送。

    像这样声明套接字:

    int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW)
    

    发生错误时,作为套接字文件描述符的 sockfd 将返回 -1。

    以太网帧的结构可以这样构造:

    struct ethernet_msg{
            char destination_mac[6];
            char source_mac[6];
            char transport_protocol[2];
            char data1[6];
            char data2[6];
            char data3[8];
            char data4[8];
            char data5[8];
            char data6[2];
            char data7[2];
            char data8[6];
            char data9[4];
            char data10[6];
    };
    struct ethernet_msg my_msg = {
            {0x91, 0xe0, 0xf0, 0x01, 0x00, 0x00},//destination_mac
            {0x08, 0x00, 0x27, 0x90, 0x5f, 0xae},//source_mac
            {0x22, 0xf0},//transport_protocol
            {0xfc, 0x06, 0x00, 0x2c, 0x00, 0x00},//data1
            {0x00, 0x00, 0x00, 0x00, 0x00, 0x00},//data2
            {0x08, 0x00, 0x27, 0xff, 0xfe, 0x90, 0x5f, 0xae},//data3
            {0xd8, 0x80, 0x39, 0xff, 0xfe, 0xd0, 0xac, 0xb5},//data4
            {0xd8, 0x80, 0x39, 0xff, 0xfe, 0xd0, 0x9b, 0xc8},//data5
            {0x00, 0x00},//data6
            {0x00, 0x00},//data7
            {0x00, 0x00, 0x00, 0x00, 0x00, 0x00},//data8
            {0x00, 0x00, 0x00, 0x5f},//data9
            {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}//data10
    };
    

    注意我是如何在框架内放置不同长度的字符数组的。您可以根据需要进行设置。获取要发送的接口的 MAC 地址:

    void get_interface_MAC(char interface_name[IFNAMSIZ], int sockfd)
    {
            struct ifreq if_mac;
            memset(&if_mac, 0, sizeof(struct ifreq));
            strncpy(if_mac.ifr_name, interface_name, IFNAMSIZ-1);
            if (ioctl(sockfd, SIOCGIFHWADDR, &if_mac) < 0)
                    perror("SIOCGIFHWADDR");
    }
    

    然后获取网络设备的索引:

    /* Get the index of the interface to send on */
    int get_interface_index(char interface_name[IFNAMSIZ], int sockfd)
    {
            struct ifreq if_idx;
            memset(&if_idx, 0, sizeof(struct ifreq));
            strncpy(if_idx.ifr_name, interface_name, IFNAMSIZ-1);
            if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0)
                    perror("SIOCGIFINDEX");
    
            return if_idx.ifr_ifindex;
    }
    
    socket_address.sll_ifindex = get_interface_index(interface_name, sockfd);
    /* Address length*/
    socket_address.sll_halen = ETH_ALEN;
    /* Destination MAC */
    memcpy(socket_address.sll_addr, avdecc_msg.destination_mac, ETH_ALEN);
    

    然后最终通过套接字发送:

    /* Send packet */
    if (sendto(sockfd, &avdecc_msg, sizeof(avdecc_msg), 0, (struct sockaddr*)&socket_address, sizeof(struct sockaddr_ll)) < 0)
                    printf("Send failed\n");
    

    希望对您有所帮助。 我使用了这个link 并改进了代码来实现这一点。它对我有用。

    【讨论】:

    • 该问题专门询问如何做到这一点没有 sockaddr_ll,这个答案非常使用它。看起来这可能是发送原始以太网帧的一个不错的例子,但手头的问题是 sockaddr_ll 并非在所有系统上都可用。
    【解决方案2】:

    据我所知,Enea OSE 带有这些标头

    #include <sys/socket.h>
    #include <netpacket/packet.h>
    

    后者声明 struct sockaddr_ll 应该允许您将以太网数据包发送到类型的套接字

    int sockfd = socket(AF_PACKET, type, protocol);
    

    类型可以是SOCK_DGRAMSOCK_RAW(您需要自己创建以太网标头)。

    我找不到以太网协议的正确标头。您需要在您的标头中搜索 ETH_* 定义并找到正确的以太网标头

    【讨论】:

    • 我在库中找不到 头文件。这是Enea OSE5.6,也许它没有包含在这个版本中?我也无法在 OSE 应用程序编程手册中找到它。那么我可以使用简单的 sockaddr 结构还是必须使用 sockaddr_ll?
    • struct sockaddr 只是一个通用的占位符。无论如何,您放入其中的任何内容都必须由套接字协议栈解释。因此,如果您的内核实现了原始/数据报以太网通信,那么必须有一个标头定义地址族AF_*、协议和适当的结构来保存协议控制信息。
    【解决方案3】:

    我没有一个好的答案,但是在 Linux 中,当你绑定地址后,你可以使用 write。 那你为什么要使用 sendto() 呢?

    首先在 Linux 中发送原始数据包(我不知道您的 RTOS,但您应该在该操作系统提供的包含文件中查找缺少的定义) 您需要在系统上成为 root 或使用 setcap 命令添加原始功能 例如:

    sudo setcap 'CAP_NET_RAW+eip CAP_NET_ADMIN+eip' /path/to/your/executable
    

    然后在你的程序中:

    #include <stdlib.h>
    #include <ctype.h>
    #include <unistd.h>
    #include <string.h>
    
    #include <sys/socket.h>
    #include <sys/ioctl.h>
    #include <linux/if.h>
    #include <linux/if_packet.h>
    #include <linux/if_ether.h>
    
    int main()
    {
        char* ifname = "eth0";
        unsigned char srcMac[6];
        unsigned char dstMac[6] = {0, 0xDE, 0xAD, 0xBE, 0xEF, 0};
        int fd = socket( PF_PACKET, SOCK_RAW, htons( ETH_P_ALL ) );
    
         /* bind to interface */
        struct ifreq req;
        memset( &req, 0, sizeof( req ) );
        strcpy( (char*)req.ifr_name, (char*)ifname );
    
        if ( ioctl( fd, SIOCGIFINDEX, &req ) < 0 )
        {
            perror( "init: ioctl" );
            close( fd );
            return -1;
        }
    
        struct sockaddr_ll addr;
        memset( &addr, 0, sizeof( addr ) );
        addr.sll_family   = PF_PACKET;
        addr.sll_protocol = 0;
        addr.sll_ifindex  = req.ifr_ifindex;
    
        if ( bind( fd, (const struct sockaddr*)&addr, sizeof( addr ) ) < 0 )
        {
            perror( "init: bind fails" );
            close( fd );
            return -1;
        }
    
        if ( ioctl( fd, SIOCGIFHWADDR, &req ) < 0 )
        {
            perror( "init: ioctl SIOCGIFHWADDR" );
            close( fd );
            return -1;
        }
    
        /* store your mac address somewhere you'll need it! in your packet */
        memcpy( srcMac, (unsigned char*)req.ifr_hwaddr.sa_data, ETH_ALEN );
    
        int length = 60;
        unsigned char txPkt[ length ];
        unsigned char* pkt = txPkt;
        memset( txPkt, 0, sizeof( txPkt ) );
    
        //destination mac address
        memcpy( pkt, dstMac, 6 );
        pkt += 6;
    
        // source mac address
        memcpy( pkt, srcMac, 6 );
        pkt += 6;
    
        // add more data, like a type field!  etc
    
        int bytes = write( fd, txPkt, length );
    }
    

    当您编写原始数据包时,您必须自己输入 MAC 地址等。 FCS 可以省略(应由硬件处理),因此最小数据包大小为 60 字节,我相信您必须发送 60 字节。 如果您在传出卡上捕获,您将看不到 FCS。

    我想你应该可以使用 sendto() 函数而不是 write 但请注意 struct sockaddr_ll 不包含任何目标信息,所以何必呢?

    【讨论】:

    • 显然这个问题与 linux 无关。
    猜你喜欢
    • 2013-04-20
    • 2011-12-02
    • 1970-01-01
    • 2015-01-28
    • 2014-02-21
    • 2020-01-10
    • 2011-04-27
    • 2015-05-20
    • 1970-01-01
    相关资源
    最近更新 更多